Примечание редактора: как мы все знаем, самой большой особенностью JS является асинхронность.Асинхронность повышает производительность, но создает некоторые трудности при написании, создавая возмутительный ад обратных вызовов. Для решения этой проблемы предлагалось одно решение за другим. Сегодня мы пригласили г-на Ли Сонгфэна, известного переводчика нескольких книг, таких как «Расширенное программирование на JavaScript», объяснить нам решения и коннотации написания различных асинхронных функций.
Этот контент основан на текстовой версии, опубликованной ранее. Если вы хотите увидеть ключевые моменты, вы можете прочитать предыдущую PPT:ppt.baomitu.com/d/fd045abb
Также ознакомьтесь с предыдущими общими видео:cloud.live.360v cloud.net/theater/para…
ES7 (ECMAScript 2016) представил асинхронные функции (async/await
), реализует метод последовательного и синхронного написания кода для управления асинхронным процессом и полностью решает проблему «ада обратных вызовов», от которой страдают разработчики JavaScript. Например, асинхронная логика, которая ранее требовала вложенных обратных вызовов:
const result = [];
// pseudo-code, ajax stand for an asynchronous request
ajax('url1', function(err, data){
if(err) {...}
result.push(data)
ajax('url2', function(err, data){
if(err) {...}
result.push(data)
console.log(result)
})
})
Теперь его можно написать в стиле следующего синхронного кода:
async function example() {
const r1 = await new Promise(resolve =>
setTimeout(resolve, 500, 'slowest')
)
const r2 = await new Promise(resolve =>
setTimeout(resolve, 200, 'slow')
)
return [r1, r2]
}
example().then(result => console.log(result))
// ['slowest', 'slow']
Асинхронная функция должна быть вfunction
добавить передasync
ключевое слово, а внутренне начиная сawait
ключевое слово, чтобы "блокировать" асинхронную операцию до тех пор, пока асинхронная операция не вернет результат, а затем продолжить выполнение. До появления функции Async мы не могли представить, что следующий асинхронный код может получить результат напрямую:
const r1 = ajax('url')
console.log(r1)
// undefined
Это конечно невозможно, результат асинхронной функции можно получить только в обратном вызове. Можно сказать, что асинхронная функция является результатом усилий программистов JavaScript по «самоспасению» после того, как они наступили на «яму» в процессе изучения способов эффективного асинхронного программирования, а не на «конфетке». Однако читатели могут не знать, что функция Async на самом деле является синтаксическим сахаром (это действительно «конфетка»?), за ней стоят Promise, Iterator и Generator, представленные в ES6 (ECMAScript 2015), которые мы называем сокращенно «PIG». В этой статье вы попробуете этот синтаксический сахар и почувствуете, как PIG реализует асинхронную функцию.
1. Текущее программирование на JavaScript в основном асинхронное.
Текущее программирование на JavaScript — это в основном асинхронное программирование. Почему ты это сказал? Веб-страница или веб-разработка началась с популярности Ajax в 2005 году и постепенно перешла к эпохе интенсивного взаимодействия. Особенно после популярности SPA (одностраничное приложение) некоторые люди однажды предложили, чтобы «веб-страницы были превращены в веб-приложения, и они должны быть сопоставимы с собственными приложениями». Сегодня Angular, React и Vue, рожденные в контексте компонентизации фронтенд-разработки, являются результатом дальнейшей эволюции SPA.
Веб-приложения или разработка все чаще характеризуются интенсивным взаимодействием, что это значит? Это означает, что согласно характеристикам времени выполнения браузера, в процессе первой загрузки страницы, основной задачей, связанной с JavaScript, является загрузка базовой библиотеки времени выполнения и библиотеки расширений (включая скрипт для исправления браузера младшей версии), и затем инициализируйте и установите статус страницы. После первой загрузки пользовательские операции на странице, ввод-вывод данных и обновления DOM управляются асинхронными сценариями JavaScript. Таким образом, самое большое применение программирования на JavaScript — это веб-взаимодействие, а ядром веб-взаимодействия является асинхронная логика.
Однако единственными средствами управления асинхронным потоком в JavaScript до ES6 были события и обратные вызовы. Например, следующий пример показываетXMLHttpRequest
объект отправляет асинхронный запрос, а затем даетonload
иonerror
События регистрируются для обработчиков успеха и ошибок соответственно:
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function () {
if (req.status == 200) {
processData(req.response);
}
};
req.onerror = function () {
console.log('Network Error');
};
req.send();
В приведенном ниже коде показан классический обратный вызов Node.js «ошибка первого прохода». Но здесь важно упомянуть, что этот стиль функционального программирования также называется CPS, то есть Continuation Passing Style, что я перевожу как «стиль прохождения последующих операций». потому что звонюreadFile
Передается функция обратного вызова, представляющая последующую операцию. Этот кусок не расширяется.
// Node.js
fs.readFile('file.txt', function (error, data) {
if (error) {
// ...
}
console.log(data);
}
);
Есть много проблем с событиями и обратными вызовами, в основном из-за того, что они подходят только для простых случаев. Когда логика усложняется, стоимость написания и поддержки кода возрастает в геометрической прогрессии. Например, всем известный «ад обратных вызовов». Что еще более важно, асинхронный характер шаблона обратного вызова противоречит человеческому синхронному, последовательному мышлению.
Чтобы справиться со все более сложными требованиями к асинхронному программированию, в ES6 были введены промисы для решения вышеуказанных проблем.
2. Promise
Обещание, общее понимание таково: «Обещание — это заполнитель для будущей ценности». То есть семантически объект Promise представляет собой «обещание» будущего значения, которое, если оно «выполнится» в будущем, «преобразуется» в осмысленный фрагмент данных; если «отклонение» «превращается» в «отклонение причина", что является сообщением об ошибке.
Состояние объекта Promise очень простое, состояние, которое рождается, равноpending
(в ожидании), обналичено в будущем, статус становитсяfulfilled
; отклонено, состояние становитсяrejected
.fulfilled
иrejected
Судя по всему, "устоявшееся" состояние. Вышеупомянутые переходы между состояниями необратимы, поэтому промисы просты и ими легко управлять, ха-ха.
Ниже приведены все API, связанные с Promise. Первые 3 используются для создания объектов Promise (пример будет приведен позже), первые 2 из последних 4 используются для регистрации реактивных функций (пример будет приведен позже), а последние 2 используются для управления параллелизмом и упреждение:
Следующее черезPrmoise(executor)
Подробный процесс создания экземпляра промиса конструктором: вам нужно передать «исполнитель», который получает два параметра «разрешитель» и «отклонитель», которые соответствуют переменным в коде.resolve
иreject
, роль состоит в том, чтобы изменить состояние вновь созданного объекта сpending
изменить наfulfilled
иrejected
, который возвращает как «выполнение», так и «отказ». Конечно,resolve
иreject
Все они вызываются в обратном вызове асинхронной операции. После вызова механизм планирования цикла событий в среде выполнения (движок браузера или библиотека Node.js) добавит соответствующую функцию реакции — функцию реакции чести или функцию реакции отклонения и соответствующие параметры в очередь «микрозадачи», чтобы следующий «тик» (тик) планирования для выполнения потока JavaScript.
Как упоминалось ранее, состояние объекта Promise задаетсяpending
статьfulfilled
, выполняется «реакция выполнения» и становитсяrejected
, выполняется «реакция отторжения». Как показано в примере ниже, обычным способом является передачаp.then()
Зарегистрируйте функцию выкупа черезp.catch()
Зарегистрировать функцию отклонения:
p.then(res => { // 兑现反应函数
// res === 'random success'
})
p.catch(err => { // 拒绝反应函数
// err === 'random failure'
})
Конечно, есть нетрадиционные способы, и иногда нетрадиционные способы могут работать лучше:
// 通过一个.then()方法同时注册兑现和拒绝函数
p.then(
res => {
// handle response
},
err => {
// handle error
}
)
// 通过.then()方法只注册一个函数:兑现函数
p.then(res => {
// handle response
})
// 通过.then()方法只传入拒绝函数,兑现函数的位置传null
p.then(null, err => {
// handle error
})
Вот и все промисы. В дополнение к Promise в ES6 также представлены Iterator (итератор) и Generator (генератор), поэтому существует комбинация PIG, которая реализует асинхронные функции. Давайте кратко рассмотрим Iterator и Generator соответственно.
3. Iterator
Самый простой способ понять итератор или итератор — посмотреть на его интерфейс:
interface IteratorResult {
done: boolean;
value: any;
}
interface Iterator {
next(): IteratorResult;
}
interface Iterable {
[Symbol.iterator](): Iterator
}
из серединыIterator
Смотреть.
Что такое итератор? это объект, который имеетnext()
метод, каждый вызовnext()
метод, он вернет результат итератора (см. первый интерфейсIteratorResult
). И результатом этого итератора тоже является объект, который имеет два свойства:done
иvalue
,вdone
является логическим значением,false
Указывает, что последовательность, повторяемая итератором, не закончилась;true
Указывает конец последовательности, повторяемой итератором. иvalue
Это значение, которое фактически возвращается итератором на каждой итерации.
Посмотрите на последний интерфейсIterable
, что переводится как «итерируемый объект», который имеет[Symbol.iterator]()
метод, который возвращает итератор.
Три простых и важных понятия итерируемых объектов (реализующих «итерируемый протокол»), итераторов (реализующих «итерационный протокол») и результатов итератора можно понять, объединив предыдущее определение интерфейса и следующее изображение (не имеет значения, если вы не можете понять это временно, в конце есть пример бесконечной последовательности, которая может помочь всем понять).
Итерируемые объекты — это очень знакомая концепция: массивы, строки и новые типы коллекций Set и Map в ES6 — все это итерируемые объекты. Что это значит? Это означает, что мы можем использовать 3 новых синтаксиса E6 для управления итерируемыми объектами:
for...of
[...iterable]
Array.from(iterable)
УведомлениеСледующий синтаксис до E6 не работает с итерируемыми объектами:
for...in
Array#forEach
Далее мы рассмотрим пример.
for (const item of sequence) {
console.log(item)
// 'i'
// 't'
// 'e'
// 'r'
// 'a'
// 'b'
// 'l'
// 'e'
}
console.log([...sequence])
// ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e']
console.log(Array.from(sequence))
// ['i', 't', 'e', 'r', 'a', 'b', 'l', 'e']
В приведенных выше примерах используетсяfor...of
, оператор спреда (...
)иArray.from()
метод для перебора ранее определенногоsequence
этот итерируемый объект.
Давайте посмотрим на небольшой пример создания бесконечной последовательности с помощью итератора, На этом примере мы лучше поймем концепции, связанные с итераторами.
const random = {
[Symbol.iterator]: () => ({
next: () => ({ value: Math.random() })
})
}
// 运行这行代码会怎么样?
[...random]
// 这行呢?
Array.from(random)
В этом примере определяются два метода с использованием двух стрелочных функций ES6 и создаются три объекта.
самый внутренний объект{ value: Math.random() }
Очевидно, "результат итератора" (IteratorResult
) объект, потому что он имеетvalue
атрибуты и..., подождите,done
характеристики? здесь нет определенияdone
свойств, поэтому каждая итерация (вызовnext()
) при доступеIteratorResult.done
вернусьfalse
; поэтому определение этого результата итератора эквивалентно{ value: Math.random() , done: false }
. Очевидно,done
никогда не может бытьtrue
, так что это бесконечная последовательность случайных чисел!
interface IteratorResult {
done: boolean;
value: any;
}
Если смотреть дальше, то стрелочная функция, которая возвращает объект результата этого итератора, присваивается внешнему объекту.next()
метод. в соответствии сIterator
определение интерфейса, если объект содержитnext()
метод, а возвращаемое значение этого метода является результатом итератора, так что же это за объект? Правильно, это итератор. Итак, второй объект — это итератор!
interface Iterator {
next(): IteratorResult;
}
Если посмотреть дальше, функция стрелки, которая возвращает этот объект итератора, присваивается внешнему объекту.[Symbol.iterator]()
метод. в соответствии сIterable
определение интерфейса, если объект содержит[Symbol.iterator]()
метод, а возвращаемое значение этого метода — итератор, так что же это за объект? Да, это итерируемый объект.
interface Iterable {
[Symbol.iterator](): Iterator
}
Что ж, к настоящему моменту у нас должно быть полное представление об итераторах и связанных с ними понятиях. Продолжайте смотреть на пример ниже. В предыдущем примере определяется итерируемый объектrandom
, итератор этого объекта может бесконечно возвращать случайные числа, поэтому:
// 运行这行代码会怎么样?
[...random]
// 这行呢?
Array.from(random)
Да, обе строки кода вызывают сбой программы (или среды выполнения)! Потому что итератор будет продолжать работать, блокируя поток выполнения JavaScript и, в конечном итоге, может привести к тому, что среда выполнения перестанет отвечать на запросы или даже выйдет из строя из-за заполнения доступной памяти.
Так как же правильно получить доступ к бесконечной последовательности? Ответ заключается в использовании деструктурирующего присвоения или предоставленияfor...of
Цикл устанавливает условие выхода:
const [one, another] = random // 解析赋值,取得前两个随机数
console.log(one)
// 0.23235511826351285
console.log(another)
// 0.28749457537196577
for (const value of random) {
if (value > 0.8) { // 退出条件,随机数大于0.8则中断循环
break
}
console.log(value)
}
Конечно, есть более продвинутые способы использования бесконечных последовательностей, и в рамках этой статьи я не буду их здесь описывать. Давайте поговорим о последнем генераторе функций ES6.
4. Generator
Например, на интерфейсе:
interface Generator extends Iterator {
next(value?: any): IteratorResult;
[Symbol.iterator](): Iterator;
throw(exception: any);
}
Можете ли вы сказать, что такое генератор? Только из-за своего интерфейса это одновременно и итератор, и итерируемый объект. Да, поэтому генераторы являются «расширенными» версиями итераторов, почему? потому что генератор также предоставляет ключевое словоyield
, который возвращает значение последовательности, автоматически заключенное вIteratorResult
(результат итератора), что избавляет нас от необходимости писать соответствующий код вручную. Вот определение генераторной функции:
function *gen() {
yield 'a'
yield 'b'
return 'c'
}
Эй, разве генератор, определяемый интерфейсом, не является объектом, как это функция?
На самом деле неверно говорить, что генераторы — это объекты или функции. Но мы знаем, что вызов функции-генератора вернет итератор (объект, описываемый интерфейсом), и этот итератор может управлять логикой и данными, инкапсулированными функцией-генератором, которая его возвращает. В этом смысле генератор состоит из двух частей: функции-генератора и возвращаемого ею итератора. Другими словами, генератор — это общее понятие, общий термин. (Не волнуйтесь, вы увидите, что значит понимать генераторы таким образом.)
В начале этого раздела генератор (возвращаемый объект) «является одновременно итератором и итерируемым объектом». Давайте проверим это:
const chars = gen()
typeof chars[Symbol.iterator] === 'function' // chars是可迭代对象
typeof chars.next === 'function' // chars是迭代器
chars[Symbol.iterator]() === chars // chars的迭代器就是它本身
console.log(Array.from(chars)) // 可以对它使用Array.from
// ['a', 'b']
console.log([...chars]) // 可以对它使用Array.from
// ['a', 'b']
Мы получили все ответы через комментарии в коде. Вот небольшой вопрос: "Почему перебор этого генератора возвращает значения последовательности, которые не содержат символов'c'
Шерстяная ткань? "
причина в том,yield
Возвращенный объект результата итератораdone
стоимость недвижимостиfalse
,так'a'
и'b'
все действительные значения последовательности; иreturn
Хотя возвращаемый объект также является результатом итератора, ноdone
Стоимость имуществаtrue
,true
указывает на конец последовательности, поэтому'c'
не будет включено в результат итерации. (если нетreturn
оператор, код выполняется до конца функции-генератора и неявно возвращается{ value: undefined, done: true}
. Хотите верьте, хотите нет, вы это знаете. )
Вышеупомянутое является лишь одним из аспектов генераторов как «расширенных» итераторов. Далее, давайте коснемся другой стороны действительно мощных генераторов!
В чем генераторы действительно эффективны и что отличает их от итераторов, так это то, что они могут не только возвращать значение на каждой итерации, но и получать значение. (Конечно, в концепции генераторов есть функции-генераторы! Конечно, функции могут получать параметры.) Подождите, в функции-генераторы можно не только передавать параметры, но и даватьyield
Параметры выражения!
function *gen(x) {
const y = x * (yield)
return y
}
const it = gen(6)
it.next()
// {value: undefined, done: false}
it.next(7)
// {value: 42, done: true}
В примере с простым генератором выше. Определим генераторную функцию*gen()
, который принимает параметрx
. В теле функции есть только одинyield
Выражение, кажется, ничего не делать. но,yield
Выражение кажется «заполнителем для значения», потому что код оценивает переменную в какой-то момент.x
Умножьте это «значение» и назначьте этот продукт переменной.y
. Наконец, функция возвращаетy
.
Это немного озадачивает, давайте проанализируем это шаг за шагом.
- перечислить
gen(6)
Итераторы, создающие генераторыit
(Как упоминалось ранее, генератор содержит итератор и функцию-генератор, которая его возвращает), и ему передается значение 6. - перечислить
it.next()
Запустите генератор. В этот момент код функции-генератора выполняется до первогоyield
Пауза в выражении и возвратundefined
. (yield
Он не простаивает, он видит, что нет явного возвращаемого значения, поэтому он может вернуть только значение по умолчанию.undefined
. ) - перечислить
it.next(7)
Возобновить выполнение генератора. В настоящее времяyield
Получив пришедшее значение 7, немедленно возобновляет выполнение кода функции-генератора и заменяет себя значением 7. - Расчет кода:
6 * 7
, получить 42 и присвоить 42 переменнойy
, и, наконец, вернутьсяy
. - Функция генератора возвращает окончательное значение:
{value: 42, done: true}
.
В этом примере есть только одинyield
, если их большеyield
, то шаг 4 перейдет ко второмуyield
Снова приостановите выполнение функции-генератора, верните значение, а затем повторите шаги 3 и 4, то есть вы также можете вызватьit.next()
Передать значения в функцию-генератор.
Кратко подытожим, каждый звонокit.next()
, следующие 4 ситуации могут привести к приостановке или остановке работы генератора:
-
yield
выражение возвращает следующее значение в последовательности -
return
оператор возвращает значение функции-генератора ({ done: true }
) -
throw
оператор полностью останавливает выполнение генератора (подробнее об этом позже) - В конце функции генератора неявно верните
{ value: undefined, done: true}
Уведомлениездесь
return
иthrow
Его можно вызвать либо внутри функции-генератора, либо вне функции-генератора через итератор генератора, например:it.return(0)
,it.throw(new Error('Oops'))
. Соответствующие примеры мы приведем позже.
Отсюда мы знаем, что уникальность генератора заключается в егоyield
ключевые слова. этоyield
Есть две волшебные вещи: во-первых, это демаркационная точка, где функция-генератор приостанавливает и возобновляет выполнение; во-вторых, это среда для передачи значений (включая ошибки/исключения) наружу и внутрь.
Говоря об ошибках/исключениях, давайте сосредоточимся на том, как генераторы обрабатывают исключения. В конце концов, обработка ошибок — одна из самых больших головных болей для программистов JavaScript при написании асинхронного кода с использованием обратных вызовов.
4.1 Синхронная обработка ошибок
Во-первых, мы рассмотрим распространение ошибок «изнутри наружу», т. е. вбрасывание ошибок в код итератора изнутри функции-генератора.
function *main() {
const x = yield "Hello World";
yield x.toLowerCase(); // 导致异常!
}
const it = main();
it.next().value; // Hello World
try {
it.next( 42 );
} catch (err) {
console.error(err); // TypeError
}
Как следует из комментариев к коду, вторая строка кода функции-генератора вызовет исключение (почему, читатель может выполнить код самостоятельно и сделать вывод). Поскольку внутри функции-генератора нет обработки исключений, ошибка передается в итеративный код генератора, т.е.it.next(42)
эта строка кода. К счастью, эта строка кода заменена наtry/catch
завернутый, ошибки могут быть обнаружены и обработаны в обычном режиме.
Далее, посмотрите на передачу ошибок "снаружи внутрь" (точнее, должно быть "снаружи внутрь и наружу").
function *main() {
var x = yield "Hello World";
console.log('never gets here');
}
const it = main();
it.next().value; // Hello World
try {
it.throw('Oops'); // `*main()`会处理吗?
} catch (err) { // 没有!
console.error(err); // Oops
}
Как видно из кода, итерационный код проходит черезit.throw('Oops')
Сбросить исключение. Это исключение выбрасывается в функцию-генератор (через итераторit
). После того, как его бросили,yield
Expression обнаружил, что получил «горячую картошку», и огляделся, а «сопровождения» логики обработки исключений не было, поэтому он снова быстро выдал исключение. итераторit
Очевидно, он подготовлен, и его первоначальная цель — посмотреть, есть ли какая-то логика внутри функции-генератора, отвечающей за обработку исключений (см. комментарий "//*main()
Будет ли с этим разбираться? "),"нет! ",этоtry/catch
Давно ждал.
4.2 Асинхронные итерационные генераторы
Итеративная передача значений генераторам, которую мы видели ранее, включая передачу ошибок, является синхронной. На самом деле генераторyield
Настоящая (о, еще одна «настоящая») сила выражения заключается в том, что ему не нужно ждать, пока код итератора будет вызван синхронно после приостановки выполнения кода генератора.it.next()
метод, чтобы дать ему возвращаемое значение, но вы можете позволить итератору получить возвращаемое значение в обратном вызове асинхронной операции, а затем передатьit.next(res)
Передайте ему значение.
ты понимаешь?yield
Может дождаться результата асинхронной операции. Это делает возможной, казалось бы, невозможную ситуацию, упомянутую в начале этой статьи:
const r1 = ajax('url')
console.log(r1)
// undefined
Как его изменить, добавить один перед асинхронной операциейyield
Ах:
const r1 = yield ajax('url')
console.log(r1)
// 这次r1就是真正的响应结果了
Проиллюстрируем это на примере асинхронной операции, возвращающей Promise. Благодаря асинхронным операциям на основе обратного вызова легко преобразовать асинхронные операции на основе промисов (такие как$.ajax()
или поutil.promisify
Преобразование асинхронных методов в Node.js в обещания).
Вот пример. Это чистый пример Promise.
function foo(x,y) {
return request(
"http://some.url.1/?x=" + x + "&y=" + y
);
}
foo(11, 31)
.then(
function(text){
console.log(text);
},
function(err){
console.error(err);
}
);
функцияfoo(x, y)
инкапсулирует асинхронныйrequest
Запрос, который возвращает обещание. перечислитьfoo(11, 31)
После передачи параметровrequest
Просто отправьте запрос на объединенный URL и верните Pending (pending
) состояние объекта Promise. Если запрос выполнен успешно, выполнитеthen()
Функция ответа на погашение, зарегистрированная в , обрабатывает ответ; если запрос завершается неудачей, выполняется функция ответа на отказ и обрабатывается ошибка.
Далее нам нужно сделать приведенную выше комбинацию генератора кода, чтобы генератор занимался только отправкой запроса и получением результатов ответа, ожиданием логики обработки обратного вызова и асинхронными операциями, поскольку абстрагирует детали реализации. («Что касается деталей», да, наша цель — сосредоточиться только на обработке запросов и результатов. Ну, все подробности, ха-ха ~.)
function foo(x, y) {
return request(
"http://some.url.1/?x=" + x + "&y=" + y
);
}
function *main() {
try {
const result = yield foo(11, 31); // 异步函数调用!
console.log( result );
} catch (err) {
console.error( err );
}
}
const it = main();
const p = it.next().value; // 启动生成器并取得Promise `p`
p.then( // 等待Promise `p`解决
function(res){
it.next(res); // 把`text`传给`*main()`并恢复其执行
},
function(err){
it.throw(err); // 把`err`抛到`*main()`
}
);
Обратите внимание, что генераторная функция (*main
)изyield
Асинхронный вызов функции происходит в выражении:foo(11, 31)
. И все, что нам нужно сделать, это передать код итератораit.next()
Возьмите обещание, возвращенное этим вызовом асинхронной функции, и обработайте его должным образом. Как с этим бороться? Давайте посмотрим на код.
После создания итератора генератораconst p = it.next().value;
Обещание возвращеноp
. существуетp
В функцию отклика выкупа положим откликres
пройти черезit.next(res)
Вызов возвращаетyield
.yield
получить ответres
После этого выполнение немедленно возобновляется генератором кода,res
назначить переменнойresult
. Таким образом, мы успешно получили результат ответа на асинхронный запрос в функции-генераторе, написав синхронный код! Удивительно, не так ли?
(Конечно, если при асинхронном запросе возникает ошибка, вp
в функции реакции отклоненияit.throw(err)
Выбросьте ошибку в функцию-генератор. Но это не имеет значения сейчас. )
Что ж, достижение цели: мы используем генератор синхронного кода для достижения идеального контроля над асинхронной операцией. Однако есть проблема. В приведенном выше примере пакет генератора только асинхронная операция, если несколько асинхронных операций, как это сделать? В настоящее время существует предпочтительно общая для обработки функция генератора кода, которая включает в себя независимо от того, сколько асинхронных операций, код может автоматически получать промис, и ожидает ответа/распространения ошибки и т. д. «Детали» работают.
Разве это не бегун генератора на основе Promise?
5. Обычный бегунок генератора
Подводя итог, мы хотим получить такой результат:
function example() {
return run(function *() {
const r1 = yield new Promise(resolve =>
setTimeout(resolve, 500, 'slowest')
)
const r2 = yield new Promise(resolve =>
setTimeout(resolve, 200, 'slow')
)
return [r1, r2]
})
}
example().then(result => console.log(result))
// ['slowest', 'slow']
То есть определить общую функцию запускаrun
, который обрабатывает любое количество асинхронных операций, заключенных в переданную ему функцию генератора. Для каждой операции корректно возвращает асинхронный результат или выбрасывает исключение в функцию-генератор. Конечным результатом выполнения этой функции является возврат Promise, который содержит результаты всех асинхронных операций, возвращаемых функцией-генератором (пример выше).
Некоторые умные люди уже реализовали такую бегущую программу. Ниже мы приведем две реализации. Вы можете попробовать запустить ее самостоятельно, а затем выполнить ее "человеческую плоть" для углубления понимания.
УведомлениеДо того, как в ES7 появилась функция Async, программисты JavaScript, которые страдали от обратных вызовов, полагались на аналогичные запущенные программы в сочетании с генераторами, чтобы «продолжить свою жизнь». На самом деле, в «дикие дни» перед ES6 (без промисов, без генераторов) неукротимые и находчивые программисты JavaScript уже придумали/нашли реализацию Thenable (предшественника Promise) и генератороподобных реализаций (таких как регенератор) , позволяя браузерам поддерживать свои эффективные рабочие мечты о написании асинхронного кода в синхронном стиле.
Больно! Здорово! Грустный муж, искривленный!
Это:
function run(gen) {
const it = gen();
return Promise.resolve()
.then( function handleNext(value){
let next = it.next( value );
return (function handleResult(next){
if (next.done) {
return next.value;
} else {
return Promise.resolve( next.value )
.then(
handleNext,
function handleErr(err) {
return Promise.resolve(
it.throw( err )
)
.then( handleResult );
}
);
} // if...else
})(next); // handleResult(next)
}); // handleNext(value)
}
Процесс исполнения "человеческой плоти" для справки
(перечислить
run
См. код в начале этого раздела. )это
run
Функция принимает в качестве параметра функцию-генератор и сразу же создает итератор генератораit
(Поглядиrun
код функции).Затем он возвращает обещание, которое передается через
Promise.resolve()
создается напрямую.Мы даем это обещание
.then()
Метод передает функцию реакции на выполнение (эта функция должна быть вызвана, поскольку обещание выполнено) с именемhandleNext(value)
, который принимает параметрvalue
. При первом вызове значение не передается, поэтомуvalue
Значениеundefined
.Далее первый звонок
it.next(value)
Запустите генератор, пройдяundefined
. первый генераторyield
Будет возвращено обещание с состоянием ожидания, которое не будет разрешено, по крайней мере, в течение 500 мс.Переменная в это время
next
Значение{ value: < Promise [pending]>, done: false}
.Далее поставьте
next
Передайте его следующему IIFE (немедленно вызываемому функциональному выражению), эта функция называетсяhandleResult
(результат процесса).существует
handleResult(next)
Внутренне проверьте сначалаnext.done
, не равноtrue
,Входитьelse
пункт. через это времяPromise.resolve(next.value)
Упаковкаnext.value
: Подождите, пока возвращенное обещание будет разрешено, и получите строковое значение после его разрешения.'Slowest'
, а затем передается в функцию реакции выкупаhandleNext(value)
.На данный момент обработана первая половина первой асинхронной операции. Затем позвоните еще раз
handleNext(value)
передать в строке'Slowest'
. Итератор вызывается сноваnext(value)
Пучок'Slowest'
возвращает первый в функции-генератореyield
,yield
Получить эту строку, немедленно возобновить выполнение генератора и присвоить эту строку переменнойr1
. Код в функции-генераторе продолжает выполняться до тех пор, пока неyield处
Приостановить, создать и вернуть второе конечное значение в этой точке'slow'
Promise, но в это время Promise находится в состоянии ожидания и будет разрешено через 200 миллисекунд.Продолжая, в коде итератора переменная
next
снова получить объект{ value: <Promise [pending]>, done: false}
. Войдите снова в IIFE, пройдитеnext
. экзаменnext.done
не равноfalse
,существуетelse
ручка в блокеnext.value
упакован вPromise.resolve(next.value)
середина……
Смотри, вот еще:
function run(generator) {
return new Promise((resolve, reject) => {
const it = generator()
step(() => it.next())
function step(nextFn) {
const result = runNext(nextFn)
if (result.done) {
resolve(result.value)
return
}
Promise
.resolve(result.value)
.then(
value => step(() => it.next(value)),
err => step(() => it.throw(err))
)
}
function runNext(nextFn) {
try {
return nextFn()
} catch (err) {
reject(err)
}
}
})
}
6. Почему асинхронная функция является синтаксическим сахаром?
С помощью этой функции запуска мы можем сравнить следующие дваexample()
функция:
Первыйexample()
заключается в управлении асинхронным кодом через бегун генератора; второйexample()
является асинхронной (асинхронной) функцией, черезasync/await
Управление асинхронным кодом.
Разница между ними только в том, что у первого есть дополнительный слой.run
инкапсуляция функций с использованиемyield
вместоawait
, и нетasync
Модификация ключевого слова. Кроме этого, основной код точно такой же!
О чем вы думаете, когда видите асинхронную функцию, подобную следующей?
async function example() {
const r1 = await new Promise(resolve =>
setTimeout(resolve, 500, 'slowest')
)
const r2 = await new Promise(resolve =>
setTimeout(resolve, 200, 'slow')
)
return [r1, r2]
}
example().then(result => console.log(result))
// ['slowest', 'slow']
Да, асинхронные функции илиasync/await
Это кусок «синтаксического сахара», полный горечи и сладости, основанный на Promise, Iterator и Generator! Помните, асинхронная функция = Promise + Iterator + Generator, или «асинхронная функция оказывается PIG».
7. Ссылки
- ECMAScript 2018
- Practical Modern JavaScript
- You Don't Know JS: Async & Performance
- Understanding ECMAScript 6
- Exploring ES6