Тривиальный вопрос асинхронной загрузки нескольких карт (Promise и async)

внешний интерфейс TypeScript Promise Webpack
Тривиальный вопрос асинхронной загрузки нескольких карт (Promise и async)

В ежедневном процессе разработки время от времени необходимо предварительно загрузить несколько картинок одновременно, а затем работать после того, как все загружены.Комбинация промиса и асинхронного/ожидающего кода будет намного элегантнее, но также легко столкнуться с ямами, сегодня простой чат.

ES5

Давайте начнем с самого простого ES 5. Основная идея состоит в том, чтобы создать счетчик, добавлять его каждый раз, когда изображение срабатывает при загрузке, и запускать функцию обратного вызова после достижения заданного количества раз.

var count = 0,
	 imgs = [];
	 
function loadImgs(imgList, cb) {
	imgList.forEach(function(url, i) {
		imgs[i] = new Image();
		imgs[i].onload = function() {
			if( ++count === imgList.length) {
				cb && cb()
			}
		}
		imgs[i].src = url;
	})
}

Метод вызова:

loadImgs(["xxx/a.png","xxx/b.png"],function() {
	console.log("开始干活");
})

Эта базовая функция может быть удовлетворена, но этот метод обратного вызова прыгает, и код кажется запутанным.

Как говорится, высшее состояние асинхронного программирования — не заботиться о том, асинхронно оно или нет. Возможность писать асинхронный код синхронным образом — это хороший опыт кодирования. Итак, Promise и async/await вышли.

ES6

Давайте перепишем его с Promises и async/await. (обратите внимание, что в этом примере есть большая проблема)

async function loadImgs(imgList, cb) {

    console.log("start")
    for( var i =0; i<imgList.length; i++) {
        await imgLoader(imgList[i], i);
        console.log("finish"+i)
    }
    cb();
}

async function imgLoader(url, num){
    return new Promise((resolve, reject) => {
        console.log("request"+num)
        
        setTimeout(resolve, 1000);
        // let img = new Image();
        // img.onload = () => resolve(img);
        // img.onerror = reject;

        console.log("return"+num)
    })
}

loadImgs(["xxx/a.png","xxx/b.png"],function() {
	console.log("开始干活");
})

Чтобы облегчить запуск кода в среде узла, здесь я использую setTimeout вместо загрузки реального изображения.

Результат бега таков:

start
request0
return0
finish0
request1
return1
finish1
开始干活

Вы обнаружили какие-либо проблемы?Хотя мы рассчитываем написать асинхронные эффекты в виде синхронного кода, хотя мы используем async/await Promise и другие сумасшедшие вещи, фактический результат работы синхронный. После завершения request0 выдается request1.

Хотя такой код имеет четкую семантику и прост для понимания, для нас неприемлемо ждать, пока картинки загрузятся последовательно одна за другой, наша цель — выдать несколько запросов на асинхронную загрузку одновременно.

Причина этой ошибки в том, что async/await на самом деле просто синтаксический сахар, и это не означает, что он асинхронный, если он добавлен, по существу, чтобы решить проблему слишком большого количества вложенных обратных вызовов.

Перезвоните

N лет назад, распространяя оружие jQuery, все засучили рукава и присоединились к побеждающей стороне на выборах, но одной из больших проблем, с которыми они столкнулись, был «ад обратных вызовов».

Например, в следующем примере работа может быть запущена только после отправки трех ajax-запросов.

$.ajax({
    url: "xxx/xxx",
    data: 123,
    success: function () {
        $.ajax({
            url: "xxx/xxx2",
            data:456,
            success: function () {
                $.ajax({
                    url: "xxx/xxx3",
                    data:789,
                    success: function () {
                        // 终于完了可以开始干事情了
                    }
                })
            }
        })
    }
})

Это просто расписать простую структуру кода, а скобок слишком много.Если добавить бизнес-логику, обработку ошибок и т.д., то это настоящий "ад".

Обещание Спасителя?

Появление Promise значительно улучшило callback hell, а способ написания приблизился к синхронизации.

Проще говоря, промис — это контейнер, в котором хранится событие, которое завершится в будущем, и когда событие завершится, автоматически будет вызван единый интерфейс, чтобы сообщить вам об этом.

var promise = new Promise(function(resolve, reject) {
    $.ajax({
        url: "xxx/xxx3",
        success: function () {
           resolve(rs)
        },
        
    })
}

// 调用的时候
promise.then(function(rs){
	// 返回另一个 Promise
	return new Promise(...)
})
.then(function(rs){
	// 又返回另一个 Promise
	return new Promise(...)
})
.then(function(rs){
	// 开始干活
})
.catch(function(err){
	// 出错了
});

Конструктор Promise имеет два параметра, которые предоставляются движком javascript и не требуют реализации самостоятельно, а именно: разрешение и отклонение.

  • Роль решимости состоит в том, чтобы изменить состояние обещания от «незавершенной» к «решению», то есть асинхронная операция завершена, и результат может быть передан в качестве параметра на следующий шаг.
  • Роль отклонения состоит в том, чтобы изменить состояние промиса с «незавершенного» на «сбой», то есть асинхронная операция завершается сбоем и выдается ошибка.

Метод then может принимать в качестве параметров две функции, соответствующие разрешения и отклонения соответственно, где отклонение является необязательным.

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Обещания, по крайней мере, спасут большинство разработчиков от ада обратных вызовов и превратят обратные вызовы в цепочку вызовов.

Обратите внимание, что здесь в качестве примера используется ajax.На самом деле, ajax jQuery был обещан, что может быть напрямую похоже на использование Promise.

$.ajax({
  url: "test.html",
  context: document.body
}).done(function() {
  $( this ).addClass( "done" );
});

Этот способ написания гораздо более интуитивен, чем способ написания callback-функций, но все еще есть некоторые вложения, которые недостаточно интуитивно понятны.

асинхронный/ожидание приближается

На самом деле между Promise и async/await есть Генератор, который мало используется, проще говоря, форма такая:

function* gen(x){
  var y = yield x + 2;
  return y;
}

var g = gen(1);
g.next() // { value: 3, done: false }
g.next(2) // { value: 2, done: true }

Функции-генераторы помечены *, а yield используется для обозначения паузы, а функция делится на несколько частей по yield.Каждый раз при вызове next будет возвращаться объект, представляющий информацию о текущем этапе (атрибут value и done атрибут). Атрибут value — это значение выражения, следующего за оператором yield, указывающее значение текущего этапа; атрибут done — логическое значение, указывающее, была ли выполнена функция генератора, то есть есть ли следующий этап.

Дополнительную информацию о генераторе см.Уууу. Руан Ифэн.com/blog/2015/0…

async/await на самом деле является синтаксическим сахаром Generator, заменяя * более явным идентификатором, таким как async, и заменяя yield на await.

Сказав так много, мы, наконец, понимаем, что async/await — это возможность писать асинхронный код синхронным способом, решая при этом ад обратных вызовов.

Таким образом, в сценарии асинхронной загрузки нескольких изображений мы ожидаем, что нам должны сообщить об этом после завершения нескольких асинхронных операций.

async function loadImgs(imgList){
    let proList = [];
    for(var i=0; i<imgList.length; i++ ){
        let pro = new Promise((resolve, reject) => {
            console.log("request"+i)
            setTimeout(resolve, 2000);
            console.log("return"+i)
        })
        proList.push(pro)
    }

    return Promise.all(proList)
            .then( ()=>{
                console.log("finish all");
                return Promise.resolve();
            })
}

async function entry(imgList, cb) {
    await loadImgs(imgList);
    cb();
}

entry(["xxx/a.png","xxx/b.png"], function(){
    console.log("开始干活")
})

Результат бега таков:

request0
return0
request1
return1
finish all
开始干活

Вы увидите, что он будет напечатан сразу в начале

request0
return0
request1
return1

Через две секунды он распечатываетfinish all.

полный пример

Выше мы все запускаем его в командной строке узла. После понимания всего процесса давайте попробуем его в браузере. Из-за проблем совместимости нам нужно использовать веб-пакет для его преобразования.

Над кодом:


function loadImgs(imgList){
    let proList = [];
    for(var i=0; i<imgList.length; i++ ){
        let pro = new Promise((resolve, reject) => {
            let img = new Image();
            img.onload = function(){
                resolve(img)
            }
            img.src = imgList[i];
        })
        proList.push(pro)
    }

    return Promise.all(proList)
            .then( (rs)=>{
                console.log("finish all");
                return Promise.resolve(rs);
            })
}

async function entry(imgList, cb) {
    try {
        let rs = await loadImgs(imgList);
        cb(rs);
    } catch(err) {
        console.log(err)
        cb([])
    }
    
}

var imgUrlList = [
    "http://111.231.236.41/vipstyle/cartoon/v4/release/pic/index/recomment-single-s3.png",
    "http://111.231.236.41/vipstyle/cartoon/v4/release/pic/index/recomment-single-s2.png"
]
entry(imgUrlList, function(rs){
    console.log("开始干活")
    console.log(rs)
})

Обратите внимание, что объект Promise после команды await может быть отклонен, поэтому лучше поместить его в блок try...catch для выполнения.

Его нужно преобразовать с помощью webpack, вы можете обратиться к нашему webpack.config.js:

module.exports = {
  entry: ['./index.js'],
  output: {
    filename: 'bundle.js'
  },
  devtool: 'sourcemap',
  watch: true,
  module: {
    loaders: [{
      test: /index.js/,
      exclude: /(node_modules)/,
      loader: 'babel',
      query: {
        presets: ['es2015', 'stage-3'],
        plugins: [
          ["transform-runtime", {
            "polyfill":false,
            "regenerator":true
          }]
        ]
      }                                          
    }]
  }
}

После запуска напишите страницу и запустите ее в браузере, откройте консоль, там видно

微信公众号:程序员的诗和远方

Возвращаемый результат имеет два объекта изображения, чего мы и ожидали.

Снова посмотрите на сеть и проверьте, является ли она параллельной:

微信公众号:程序员的诗和远方

Окей, сделано.

one more thing

На самом деле, на предыдущем шаге связанные с асинхронной/ожидающей загрузкой изображений вещи были закончены.Здесь мы оглядываемся назад и смотрим на сгенерированные файлы, и мы обнаружим, что они очень большие.Файлы, сгенерированные только несколько строк кода на самом деле имеют 80k.

Распечатайте конкретные пакеты, которые набрал webpack, чтобы увидеть:

微信公众号:程序员的诗和远方

Среди них наш исходный index.js имеет размер всего 4.08 КБ, но webpack упаковал файл runtime.js размером 24 КБ для поддержки async/await, а также множество других файлов для поддержки синтаксиса es6.

Если вы используете при упаковкеbabel-polyfillОкончательный файл, который выходит, может быть ужасным 200k.

Так что я подумал о TypeScript.

TypeScript обладает отличными возможностями самокомпиляции, не требует дополнительного внедрения babel и работает лучше, чем babel. Возьмите мой код выше в качестве примера, после установки TypeScript вам не нужны никакие модификации, просто измените имя суффикса на ts, и вы можете начать компиляцию напрямую.

Приходите и испытайте:

微信公众号:程序员的诗和远方

bundle-ts.js скомпилирован с помощью TypeScript, только5.5k.

Взгляните на реализацию async/await в скомпилированном файле, который состоит менее чем из 40 строк и выглядит аккуратно.

微信公众号:程序员的诗和远方

Файлы, скомпилированные TypeScript, связаны с тем, сколько функций вы используете, и bable может поначалу упаковывать их для вас, даже если вы еще не используете его, а некоторые реализации TypeScript лучше, чем bable.

Конечно, это не означает, что использование TypeScript обязательно лучше, чем bable, или это зависит от реальной ситуации в проекте, но TypeScript определенно стоит вашего времени, чтобы понять.

Суммировать

Иногда мы можем не просто смотреть на проблему с поверхности, а из эволюции вещи, например, async/await на первый взгляд асинхронный, и он считается асинхронным, если его добавить, в который легко попасть недоразумение. Если у вас будет время подумать об истории, стоящей за этим, у вас будет более глубокое понимание, которым мы с вами поделимся.

Связанный код

GitHub.com/popo-chen/…

разбитые мысли

Запишите некоторые мысли, напишите о науке, технике и гуманитарных науках, напишите о жизненном положении, напишите об опыте чтения, в основном о чепухе и сантиментах. Добро пожаловать, чтобы обратить внимание и общаться.

Официальный аккаунт WeChat: Поэма программиста и дистанция

Публичный идентификатор : MonkeyCoder-Life

程序员的诗和远方

Справочная статья

Значение и использование функции генератора

Значение и использование асинхронных функций