Функция раннего обратного вызова
Мы часто пишем функции обратного вызова, такие как:
ajax(url, (res) => {
console.log(res);
})
Но у этой функции обратного вызова есть большой недостаток, то есть она будет писатьад обратного звонка(Callback hell).
Например, если несколько обратных вызовов имеют зависимости, это можно записать так:
ajax(url, (res) => {
console.log(res);
// ...处理代码
ajax(url2, (res2) => {
console.log(res2);
// ...处理代码
ajax(url3, (res3) => {
console.log(res3);
// ...处理代码
})
})
})
Этоад обратного звонка:
- Во встроенной функции есть связь, и она влияет на все тело, изменение одной повлияет на другие места.
- Есть много встроенных функций, что делать, если возникает ошибка? это загадка
Преимущества и недостатки ранних функций обратного вызова:
- Преимущество: решеносинхронная блокировка(Пока есть задача, которая занимает много времени, следующие задачи должны стоять в очереди, что задержит выполнение всей программы)
- недостаток:ад обратного звонка;Не работает
try catchпоймать ошибки; не можетreturn
план переходаGenerator
Недавно представлен ES6Generatorфункция (функция генератора), доступ к которой можно получить черезyieldКлючевое слово приостанавливает поток выполнения функции, позволяя изменить поток выполнения, тем самым предоставляя решение для асинхронного программирования. Самая большая особенностьМожет контролировать выполнение функций.
GeneratorЕсть две части, которые отличаются от обычных функций:
- один в
functionПосле этого перед именем функции стоит*, который используется для представления функции какGeneratorфункция - Внутри функции есть
yieldВыражение, используемое для определения состояния внутри функции
GeneratorКонкретное использование функции выглядит следующим образом:
- существует
GeneratorВыполните код внутри функции, если вы столкнулисьyieldключевое слово, то движок JS вернет содержимое после ключевого слова наружу и приостановит выполнение функции. - Внешние функции могут быть
nextМетод возобновляет выполнение функции.
function* fn() {
console.log("one");
yield '1';
console.log("two");
yield '2';
console.log("three");
return '3';
}
передачаGeneratorФункция аналогична вызову обычной функции, добавьте имя функции после функции.()Да, ноGeneratorФункция не выполняется немедленно, как обычная функция, вместо этогоВозвращает указатель на объект внутреннего состояния, поэтому вызовите объект итератораIteratorизnextметод,Указатель начнет выполняться с начала функции или с того места, где он остановился..
следующим образом:
nextметод:
В основном,nextКогда метод не передает параметры,yieldВозвращаемое значение выраженияundefined. когдаnextКогда параметр передается, параметр будет использоваться как предыдущий шагyieldВозвращаемое значение.
GeneratorГенератор тоже пишет асинхронный код синхронным способом, и он тоже может решить проблему callback hell, но это сложно понять.Надеюсь следующий пример поможет вам разобратьсяGeneratorСтроитель:
function* sum(a) {
console.log('a:', a);
let b = yield 1;
console.log('b:', b);
let c = yield 2;
console.log('c:', c);
let sum = a + b + c;
console.log('sum:', sum)
return sum;
}
-
nextКогда никакие параметры не передаются,yieldвернутьundefined
Как показано ниже:
-
при первом исполнении
next, аргументы игнорируются, и функция останавливается наyield 1, так что возвращайся1 -
при выполнении во второй раз
nextЕсли параметр не передается, тоyield 1то, что возвращаетсяundefined,такbЗначениеundefined -
В третий раз то же самое,
cценностьundefined -
когда
nextКогда параметр передается, параметр будет использоваться как предыдущий шагyieldВозвращаемое значение
Как показано ниже:
- при первом исполнении
next, передать параметр (20) игнорируется, и функция приостанавливается вyield 1, так что возвращайся1 - при выполнении во второй раз
next, передать параметр30, так какyield 1возвращаемое значение, поэтомуb = yield 1,bЗначение30 - при выполнении во второй раз
next, передать параметр40, так какyield 2возвращаемое значение, поэтомуc = yield 2,cЗначение40
сопрограмма
мы знаем,async/awaitэто автоматическийGeneratorфункция, описанная вышеGeneratorФункция, то нужно ввестиКак двигатель V8 реализует приостановку и возобновление функциичто о?
Чтобы понять, почему функции могут быть приостановлены и возобновлены, сначала необходимо понятьсопрограммаКонцепция чего-либо. Все мы знаем процессы и потоки, так что же такое сопрограмма?
Корутины легче, чем потоки. Вы можете думать о сопрограммах как о задачах, выполняемых в потоках.В потоке может существовать несколько сопрограмм, но одновременно в потоке может выполняться только одна сопрограмма.Например, текущее выполнение — это сопрограмма A, чтобы запустить сопрограмму B, затем сопрограмма A должна передать управление основным потоком сопрограмме B, что отражается в том, что сопрограмма A приостанавливает выполнение, а сопрограмма B возобновляется. выполнение; Точно так же сопрограмма A также может быть запущена из сопрограммы B. как правило,Если сопрограмма B запускается из сопрограммы A, мы называем сопрограмму A родительской сопрограммой сопрограммы B..
Точно так же, как процесс может иметь несколько потоков, поток может иметь несколько сопрограмм. Самое главное, сопрограммы не управляются ядром операционной системы, а полностью управляютсяс программным управлением(то есть выполняется в пользовательском режиме). Преимущество этого заключается в том, что производительность была значительно улучшена, и он не будет потреблять ресурсы, такие как переключение потоков.
Можно комбинировать с кодом, чтобы понять:
function* genDemo() {
console.log("开始执行第一段")
yield 'generator 2'
console.log("开始执行第二段")
yield 'generator 2'
console.log("开始执行第三段")
yield 'generator 2'
console.log("执行结束")
return 'generator 2'
}
console.log('main 0')
let gen = genDemo()
console.log(gen.next().value)
console.log('main 1')
console.log(gen.next().value)
console.log('main 2')
console.log(gen.next().value)
console.log('main 3')
console.log(gen.next().value)
console.log('main 4')
Процесс выполнения показан на рисунке ниже, и вы можете сосредоточиться на переключении между сопрограммами:
На рисунке мы видим четыре правила сопрограммы:
- вызвав функцию генератора
genDemoсоздатьсопрограммаgen, после создания,genСопрограмма не выполняется немедленно. - позволить
genвыполнение сопрограммы,нужно позвонитьgen.next. - Когда сопрограмма выполняется, вы можетепройти через
yieldКлючевое слово для паузыgenвыполнение сопрограмми вернуть основную информацию родительской сопрограмме. - Если сопрограмма встречает
returnключевое слово, то движок JS завершит текущую сопрограмму иreturnСледующее содержимое возвращается в родительскую сопрограмму.
Переключение между Coroutines:
-
genСопрограмма и родительская сопрограмма выполняются интерактивно в основном потоке, а не одновременно.пройти черезyieldа такжеgen.nextзавершитьиз. - когда в
genвызывается в сопрограммеyieldметод, движок JS сохранитgenИнформация о текущем стеке вызовов сопрограммы и восстановление информации о стеке вызовов родительской сопрограммы. Аналогично, при выполнении в родительской сопрограммеgen.next, JS-движок сохранит информацию о стеке вызовов родительской сопрограммы и восстановит ее.genИнформация о стеке вызовов сопрограммы.
На самом деле в JSGeneratorГенераторы — это реализация сопрограмм.
зрелое решениеPromise
оPromise, вы можете перейти к моей последней статье:«Обещание асинхронного программирования: от использования до рукописной реализации (4200 слов)», подробно описано в этой статьеPromiseКак решить проблему callback hell, разбираемсяPromiseИ происхождение микрозадач, а затем пошаговая деконструкция почерка для достижения простойPromise, и, наконец, кратко представил и реализовал некоторые вручнуюPromiseAPI, в том числеPromise.all,Promise.allSettled,Promise.race,Promise.finallyи т.д. API.
окончательное решениеasync/await
использоватьPromiseОтличное решение для ада обратного вызова, но этот путь полонPromiseизthen()метод, если поток обработки более сложный, то весь код будет заполненthen, семантика неочевидна, и код не может хорошо представить поток выполнения.
По этой причине ES7 представилasync/await, значительное улучшение по сравнению с асинхронным программированием в JavaScript, обеспечивающееВозможность использовать синхронный код для асинхронного доступа к ресурсам без блокировки основного потока.и сделать логику кода более понятной.
фактическиasync/awaitСекрет технологии заключается в том,Promiseа такжеGeneratorПриложение генератора на низком уровнеМикрозадачи и приложения сопрограмм. выяснитьasyncа такжеawaitработает, мы получили это правильноasyncа такжеawaitОтдельный анализ.
async
asyncчто именно? Согласно определению MDN,asyncпропускВыполнять асинхронно и возвращать неявноPromiseфункционировать в результате. Сосредоточьтесь на двух словах: асинхронное выполнение и неявный возврат.Promise.
Давайте посмотрим, как вернуть неявноPromise, обратитесь к следующему коду:
async function async1() {
return '秀儿';
}
console.log(async1()); // Promise {<fulfilled>: "秀儿"}
Выполните этот код, вы можете увидеть вызовasyncобъявленasync1функция возвращаетPromiseобъект, состояниеresolved, возвращенный результат выглядит следующим образом:Promise {<fulfilled>: "秀儿"}. а такжеPromiseсвязанные звонкиthenВозвращаемое значение обрабатывается таким же образом.
await
awaitнужно следоватьasyncИспользуйте его в сочетании со следующим кодом, чтобы увидетьawaitчто именно это:
async function foo() {
console.log(1)
let a = await 100
console.log(a)
console.log(2)
}
console.log(0)
foo()
console.log(3)
стоятьсопрограммаДавайте посмотрим на общую блок-схему выполнения этого кода с точки зрения:
В сочетании с приведенным выше рисунком для анализаasync/awaitПроцесс выполнения:
- Сначала выполните
console.log(0)Это заявление, распечатайте0. - с последующей казнью
fooфункцию, потому чтоfooфункцияasyncотмечено, поэтому при входе в функцию JS-движок сохранит текущий стек вызовов и другую информацию, а затем выполнитfooв функцииconsole.log(1)заявление и распечатать1. - Быть казненным
await 100, это создаст значение по умолчаниюPromiseобъект- Код выглядит следующим образом:
let promise_ = new Promise((resolve,reject){ resolve(100) }) - в этот
promise_В процессе создания объекта вы можете увидеть вexecutorфункция называетсяresolveфункция, JS-движок отправит задачу в очередь микрозадач. - Затем JS-движок приостановит выполнение текущей сопрограммы, передаст управление основным потоком родительской сопрограмме для выполнения и
promise_Объект возвращается в родительскую сопрограмму. - Управление основным потоком было передано родительской сопрограмме.На данный момент единственное, что должна сделать родительская сопрограмма, это вызвать
promise_.thenконтролироватьpromiseизменение состояния.
- Код выглядит следующим образом:
- Далее продолжаем выполнять процесс родительской сопрограммы, выполняем
console.log(3), и распечатайте3. - Затем родительская сопрограмма завершится, перед окончанием она войдет в контрольную точку микрозадачи, а затем выполнит очередь микрозадачи.
resolve(100)Задача ожидает выполнения. Когда она будет выполнена здесь, она сработаетpromise_.thenФункция обратного вызова в , как показано ниже:
promise_.then((value) => {
// 回调函数被激活后
// 将主线程控制权交给foo协程,并将vaule值传给协程
})
- После того, как функция обратного вызова будет активирована, она передаст управление основным потоком
fooфункциональные сопрограммы, и в то же времяvalueзначение передается сопрограмме. -
fooПосле активации COROUTINE, предыдущийvalueзначение, присвоенное переменнойa,ПотомfooСопрограмма продолжает выполнять последующие операторы и возвращает управление родительской сопрограмме после завершения выполнения.
Вышеупомянутоеawait/asyncпроцесс исполнения. просто такasyncа такжеawaitЗа кулисами выполняется много работы, поэтому мы можем писать асинхронный код синхронным способом.
Конечно, есть и недостатки, потому чтоawaitПреобразование асинхронного кода в синхронный код, если несколько асинхронных кодов не имеют зависимостей, но используютсяawaitприведет к снижению производительности.
async/awaitСуммировать
-
PromiseМодель программирования по-прежнему наполнена большим количествомthenметод хоть и решает проблему callback hell, но все же имеет недочеты в семантике, а в коде полно многоthenфункция, этоasync/awaitПричина появления. - использовать
async/awaitМожно написать асинхронный код в стиле синхронного кода, потому чтоasync/awaitИспользуемая базовая технологияGeneratorгенератор иPromise,GeneratorГенераторы — это реализации сопрограмм, которые используютGeneratorГенераторы можно использовать для приостановки и возобновления функций генератора. - Кроме того, двигатель V8 также
async/awaitВыполняйте переносы на уровне синтаксиса, поэтому понимание лежащего в их основе кода поможет углубить ваше пониманиеasync/awaitпонимание. -
async/awaitЭто, несомненно, очень большая инновация в области асинхронного программирования, а также основной стиль программирования в будущем. Фактически, помимо JavaScript, также были представлены такие языки, как Python, Dart и C#.async/await, его использование не только делает код чище и красивее, но и гарантирует, что функция всегда возвращаетPromise.
Сводка по асинхронному программированию
- Хотя ранняя асинхронная функция обратного вызова решала проблему синхронной блокировки, было легко написать ад обратного вызова.
-
GeneratorСамая большая особенность генераторов заключается в том, что они могут контролировать выполнение функций и являются реализацией сопрограмм. -
PromiseДля получения дополнительной информации см. мою статью:«Обещание асинхронного программирования: от использования до рукописной реализации (4200 слов)» -
async/awaitЕго можно рассматривать как окончательное решение для асинхронного программирования.Он пишет асинхронный код синхронным способом, который может поставитьawaitЭто расценивается как знак отказаться от нити и выполняется в первую очередь.asyncКод вне функции, дождитесь пустым стеком вызова, а затем перезвонитьawaitкод позади.