В ежедневном процессе разработки время от времени необходимо предварительно загрузить несколько картинок одновременно, а затем работать после того, как все загружены.Комбинация промиса и асинхронного/ожидающего кода будет намного элегантнее, но также легко столкнуться с ямами, сегодня простой чат.
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 на первый взгляд асинхронный, и он считается асинхронным, если его добавить, в который легко попасть недоразумение. Если у вас будет время подумать об истории, стоящей за этим, у вас будет более глубокое понимание, которым мы с вами поделимся.
Связанный код
разбитые мысли
Запишите некоторые мысли, напишите о науке, технике и гуманитарных науках, напишите о жизненном положении, напишите об опыте чтения, в основном о чепухе и сантиментах. Добро пожаловать, чтобы обратить внимание и общаться.
Официальный аккаунт WeChat: Поэма программиста и дистанция
Публичный идентификатор : MonkeyCoder-Life