Идеальное решение для асинхронного программирования async/await: писать асинхронный код синхронным способом.

внешний интерфейс JavaScript

Функция раннего обратного вызова

Мы часто пишем функции обратного вызова, такие как:

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метод,Указатель начнет выполняться с начала функции или с того места, где он остановился..

следующим образом:

image.png

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

Как показано ниже:

image.png

  • при первом исполненииnext, аргументы игнорируются, и функция останавливается наyield 1, так что возвращайся1

  • при выполнении во второй разnextЕсли параметр не передается, тоyield 1то, что возвращаетсяundefined,такbЗначениеundefined

  • В третий раз то же самое,cценностьundefined

  • когдаnextКогда параметр передается, параметр будет использоваться как предыдущий шагyieldВозвращаемое значение

Как показано ниже:

image.png

  • при первом исполнении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')

Процесс выполнения показан на рисунке ниже, и вы можете сосредоточиться на переключении между сопрограммами:image.png

На рисунке мы видим четыре правила сопрограммы:

  • вызвав функцию генератораgenDemoсоздатьсопрограммаgen, после создания,genСопрограмма не выполняется немедленно.
  • позволитьgenвыполнение сопрограммы,нужно позвонитьgen.next.
  • Когда сопрограмма выполняется, вы можетепройти черезyieldКлючевое слово для паузыgenвыполнение сопрограмми вернуть основную информацию родительской сопрограмме.
  • Если сопрограмма встречаетreturnключевое слово, то движок JS завершит текущую сопрограмму иreturnСледующее содержимое возвращается в родительскую сопрограмму.

Переключение между Coroutines:

  • genСопрограмма и родительская сопрограмма выполняются интерактивно в основном потоке, а не одновременно.пройти черезyieldа такжеgen.nextзавершитьиз.
  • когда вgenвызывается в сопрограммеyieldметод, движок JS сохранитgenИнформация о текущем стеке вызовов сопрограммы и восстановление информации о стеке вызовов родительской сопрограммы. Аналогично, при выполнении в родительской сопрограммеgen.next, JS-движок сохранит информацию о стеке вызовов родительской сопрограммы и восстановит ее.genИнформация о стеке вызовов сопрограммы.

image.png

На самом деле в 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)

стоятьсопрограммаДавайте посмотрим на общую блок-схему выполнения этого кода с точки зрения:

image.png

В сочетании с приведенным выше рисунком для анализа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код позади.