Кровавый случай, вызванный async, await и forEach

ECMAScript 6

Эта статья предназначена только для технической проверки, записи, общения и ни на кого не направлена. Прости за любую обиду.
Эта статья была впервые опубликована по адресу https://vsnail.cn/static/doc/blog/asyncForEach.html.

Я наткнулся на статью, в которой упоминалосьawaitсуществуетforEachне вступает в силу вasync,awaitэтоES6Грамматика в , не должна быть мне незнакомой.Я использовал ее год или два назад.Я знаю, для чего она используется и как ее использовать. Прочитав эту статью, «Седьмое чувство» чувствует, что само название кажется неуместным, а содержание вроде бы в порядке, но после просмотра резюме и остальных комментариев я всегда чувствую, что здесь должно быть недоразумение. . Так что я хочу взглянуть на "его пальто", чтобы узнать, что это за марка (хе-хе-хе... просто посмотрите на марку). После прочтения нескольких подробных вводныхasync,awaitПозже я понял, что решение написать эту статью было ошибкой. Поскольку это слишком глубоко и включает в себя слишком много, это похоже на паровую булочку в «Обещании», которая может вытянуть кучу историй; это также как сильная женская роль с предысторией, и повсюду есть драмы.

Вы можете получить все в этом мире, пока вы достаточно плохи, а вы недостаточно плохи -- "Обещание"

Ну, без лишних слов, давайте к делу. Давайте возьмем часть за частью вместе и посмотрим, что это такое?

asyncа такжеawait

ES2017введен стандартasyncфункционировать так, чтобыАсинхронные операции становятся удобнее. Хорошо, посмотрим, как это работает.


async function getBookInfo(name){
    const baseInfo = await requestBookBaseInfo(name); //requestBookBaseInfo 方法发送一个请求,向后台请求数据。这是一个异步方法
    const bookPrice = await requestBookPrice(baseInfo.id); //requestBookPrice方法发送一个请求,向后台请求数据。这是一个异步方法
    return {..baseInfo,bookPrice};
}

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

Оказывается, после использования await внутри асинхронной функции асинхронную функцию, следующую за await, можно сделать синхронной.

Предполагая, что этот вывод верен, как реализована асинхронная функция, дающая такой волшебный эффект?asyncФункция возвращает внутреннюю часть функцииreturnстоимость?awaitТолько с асинхронными функциями?

Хорошо, с этими вопросами давайте продолжим копать, чтобы увидеть, что это A, B или C, D, E, F, G. . .

Руан Дашен в "Введение в ECMAScript 6"asyncФункциональный артикль, упоминающий такое предложение»Что такое асинхронная функция? Одним словом, это синтаксический сахар для Генераторных функций."

Какой?asyncдаGeneratorсинтаксический сахар? Хорошо, тогда давайте возьмем сегодняшнюю женщину № 2,Generator.

Generator

Функции-генераторы — это решение для асинхронного программирования, предоставляемое ES6, и их синтаксическое поведение полностью отличается от традиционных функций. --- Это также то, что сказал Руан Дашен.

По моему личному пониманию,GeneratorАнглийский дословный перевод «генерировать», затемGeneratorФункция на самом деле является генератором, то, что генерируется, является итератором. Подождите еще один итератор, что это? Ну, оставим ее пока в стороне, все-таки она третья самка, поэтому не так быстро влезла. Если мы не знаем женский номер три, то мы также можемGeneratorпонимается какгосударственный менеджер. Ведь великий поэт когда-то сказал: «Глядя на хребет и образуя вершину сбоку», мы просто смотрим на самку №2 под другим углом.

Поставить в известность,GeneratorЭто обычная функция, но у нее есть еще две очевидные особенности. один в ключевом словеfunctionмежду именем функции и*; Во-вторых, используйте внутри функцииyieldВыражения, определяющие различные состояния (обратите внимание, поэтому это также называется источником машины управления состояниями).


function* childEatProcess() {
  yield 'use toilet';
  yield 'wash hands';
  yield 'sit down';
  return 'eat'
}

var ch = childEatProcess();

ch.next();//{value:'use toilet',done:false}
ch.next();//{value:'wash hands',done:false}
ch.next();//{value:'sit down',done:false}
ch.next();//{value:'eat',done:true}
ch.next();//{value:'undefined',done:true}

Приведенный выше код определяетGeneratorфункции, он имеет три внутриyield, то есть функция имеет четыре состояния (use toilet,wash hands,sit downтак же какreturnизeat).childEatProcessКак и другие функции, ее можно вызывать напрямую. А вот его возвращения (тут обязательно обратите внимание) нет.returnценность, ноОбъект, объект-указатель на внутреннее состояние, то есть объект Iterator.

а такжеGeneratorФункция аналогична подготовительной работе наших детей перед едой, если его не спровоцировать, то он сам ее не выполнит. когдаchпередачаnextПосле метода функция начинает выполняться внутри функции, и она выполняется до тех пор, покаyieldПосле ключевого слова запуститеyieldследующее выражение, а затем останавливается. Жду, когда ты снова его вызовешь (nextвызов метода)

выражение доходности

из-заGeneratorОбъект итератора, возвращаемый функцией, только вызовnextМетод будет проходить через следующее внутреннее состояние, поэтому он фактически предоставляет функцию, которая может приостановить выполнение.yieldВыражение является флагом паузы.

объект итератораnextЛогика работы метода следующая.

(1) встречаyieldвыражение, приостанавливает выполнение следующих операций и сразу последует заyieldЗначение следующего выражения в качестве возвращаемого значения объектаvalueстоимость имущества.

(2) Следующий звонокnextметод, продолжайте выполнять до следующегоyieldвыражение.

(3) Если новых не обнаруженоyieldвыражение, оно выполняется до конца функции, покаreturnзаявление, иreturnЗначение выражения, следующего за оператором, в качестве возвращаемого объектаvalueстоимость имущества.

(4) Если функция не имеетreturnоператор, возвращаемый объектvalueСтоимость свойстваundefined.

должны знать о том,yieldвыражение следует за выражением, только если вызываетсяnextМетод с внутренним указателем будет выполняться только тогда, когда внутренний указатель указывает на оператор, поэтому он равенJavaScriptОбеспечивает ручную "ленивую оценку" (Lazy Evaluation) грамматические функции.

выражения yield*

и обычныйyieldПо сравнению с выражениемyield*Выражение имеет дополнительную звездочку.yield*выражение для преобразования следующегоGeneratorвыражениевоплощать в жизнь. Это действительно сложно выразить, давайте взглянем на следующий код и почувствуем его интуитивно.


function* generator_1(){
    yield "b";
    yield "c";
}

function* generator_2(){
    yield "a";
    yield generator_1();
    yield "d";
}

function* generator_3(){
    yield "a";
    yield* generator_1();
    yield "d";
}
let g2 = generator_2();
g2.next();//{value:"a",done:false}
g2.next();//{value:Iterator,done:false}
g2.next();//{value:"d",done:true}
g2.next();//{value:undefined,done:true}

let g3 = generator_3();
g3.next();//{value:"a",done:false}
g3.next();//{value:"b",done:false}
g3.next();//{value:"c",done:false}
g3.next();//{value:"d",done:false}

Из вышеприведенного списка видно, чтоyieldтолько что казненныйgeneratorфункцию, то есть получитьgeneratorФункция сгенерированаiteratorВот и все. а такжеyield*, действительно казненныйgeneratorВнутренний указатель функции.

Тогда вы также можете кодировать


function* generator_1(){
    yield "b";
    yield "c";
}

function* generator_3(){
    yield "a";
    yield* generator_1();
    yield "d";
}

//上面的代码等价于

function* generator_4(){
    yield "a";
    yield "b";
    yield "c";
    yield "d";
}


следующий параметр

yieldСамо выражение не имеет возвращаемого значения или всегда возвращает неопределенное значение. Следующий метод может принимать один параметр, который будет использоваться как возвращаемое значение предыдущего выражения yield.. Обратите внимание, что это предложение очень важно и является основой для понимания спины. Скажи важные вещи три раза,yieldСамо выражение не имеет возвращаемого значения,yieldСамо выражение не имеет возвращаемого значения,yieldСамо выражение не имеет возвращаемого значения.

Iterator

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

IteratorЭто обходчик, представляющий собой интерфейс, обеспечивающий унифицированный механизм доступа к различным структурам данных. Любая структура данных может быть развернутаIteratorинтерфейс, операция обхода может быть завершена (то есть все члены структуры данных обрабатываются по очереди).

IteratorПроцесс обхода такой.

(1) Создайте объект-указатель, указывающий на начальную позицию текущей структуры данных. Другими словами, объект обхода по существу является объектом указателя.

(2) Первый вызов объекта указателяnextметод, вы можете указать указатель на первый член структуры данных.

(3) Второй вызов объекта указателяnextметод указатель указывает на второй член структуры данных.

(4) Постоянный вызов объекта указателяnextметод, пока он не укажет на конец структуры данных.

каждый звонокnextвсе методы возвращают информацию о текущем члене структуры данных. В частности, он возвращаетvalueа такжеdoneОбъект с двумя свойствами. в,valueсвойство является значением текущего члена,doneСвойство является логическим значением, указывающим, завершен ли обход.

Затем возьмите генератор

Я видел самку номер три(Iterator) резюме. должно быть ясноGeneratorОбъект, возвращаемый после выполнения функции, является объектом обхода внутреннего указателя, а именноIteratorобъект.IteratorОтзыв объектаnextМетод, обходGeneratorвсе вyieldопределенное состояние.

Я описал женщину номер один раньше и сказал:asyncдаgeneratorсинтаксический сахар, но до сих пор его не вижуgeneratorа такжеasyncАга отношения. Не волнуйтесь, мы потихоньку приходим. Если первый поворот,async — синтаксический сахар для генераторовЭто предложение верно, то мы, безусловно, можем использоватьgeneratorфункция для записиasyncЭффект.

БудуasyncПосле разборки можно обнаружить, что точек на самом деле две:

  1. Внутренняя асинхронная функция находится вasyncстановится синхронным, т.е.awaitПосле выполнения асинхронного выражения оно будет продолжать выполняться вниз.
  2. дляgeneratorСказать,asyncвыполняется автоматически иgeneratorто, что возвращаетсяiterator, должен быть вызванnext, выполнить.

Хорошо, тогда мы будем реализовывать эти два пункта один за другим:

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

Беда в том, чтобы позволитьGeneratorФункция запускается автоматически вместо того, чтобы мы вызывали ее вручнуюnext.

Автоматически запускать генератор

Thunkфункция выполняется автоматическиGeneratorМетод функции.

Давным-давно возник спор о «стратегии оценки», когда должны оцениваться аргументы функции. Некоторые люди думают, что выражения должны оцениваться при их использовании, чтобы избежать ненужных вычислений, что эквивалентноназывать по имени. Некоторые считают, что выражение следует вычислять перед использованием, что эквивалентновызов по значению.

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

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


function readSome(a,b,callBack){
    setTimeout(function(){
        callBack && callBack(a+b);
    },200)
}

let thunkFun = function(fn){
    return function(...args){
        return function(callBack){
           return  fn.call(this,...args,callBack);
        }
    }
}

let thunk_rs = thunkFun(readSome);

thubk_rs('Hi','girl')(function(str){
    console.log(str);
})

Вы можете спросить,Thunk函数有什么用? а такжеGeneratorПри чем здесь самоисполнение. . Не торопитесь, одежду собирают по частям и носят по частям.


function* gen() {
  // ...
}

var g = gen();
var res = g.next();

while(!res.done){
  console.log(res.value);
  res = g.next();
}


В приведенном выше кодеGeneratorфункцияgenВсе шаги выполняются автоматически. Однако это не подходит для асинхронных операций. Если необходимо обеспечить выполнение предыдущего шага до того, как можно будет выполнить следующий шаг, указанное выше автоматическое выполнение невозможно. В настоящее время,ThunkФункции могут пригодиться.


function readSome(a,b,callBack){
    setTimeout(function(){
        callBack && callBack(a+b);
    },200)
}

let thunkFun = function(fn){
    return function(...args){
        return function(callBack){
           return  fn.call(this,...args,callBack);
        }
    }
}

let thunk_rs = thunkFun(readSome);


var gen = function* (){
  var r1 = yield thunk_rs('Hi','girl');
  console.log(r1.toString());
  var r2 = yield readFileThunk('you are ','beautiful');
  console.log(r2.toString());
};

function run(fn){
    var gen = fn();
    function next(err,data){
        let rs = gen.next(data);
        if(rs.done) return ;
        rs.value(next)
    }
    next();
}

run(gen)


Кажется, что это идеально завершает автоматическое выполнение. Конечно, автоматическое выполнение происходит не только таким образом.

асинхронный принцип

Из нашего предыдущего понимания мы знаем, чтоasyncПринцип на самом делеGeneratorФункция и самоисполнитель заключены в функцию. Так что толькоasyncдаGeneratorсинтаксический сахарный аргумент. Правда раскрывается, оказывается, что женщина № 1 - это женщина № 2 в жилете, но этот жилет дает женщине № 1 некоторые особые способности. Точно так же, как Супермен должен носить свою боевую форму, чтобы называться Суперменом и обладать сверхспособностями.

Затем выберите асинхронность

Ношение жилетки естественно местами разное, хотя внутренние данные одинаковые. Итак, давайте посмотрим на разницу после того, как наденете жилет.

asyncФункцияGeneratorУлучшение функции отражено в следующих четырех пунктах.

(1) Встроенный привод. Это то, что мы называем самоисполнением.

(2) Лучшая семантика.

(3) Более широкая применимость.

(4)обещание возвращаемого значения

Наиболее важными являются первый и четвертый пункты. Пункт первый, его все на земле знают, не будем об этом.Четвертый пункт, вернуть объект обещания,а такжеgeneratorвернутьiteratorобъект. Это очень важное отличие.

ФактическиasyncКогда функция выполняется, как только она встречаетawaitвернется первым (вернутьpromiseобъект), дождитесь завершения асинхронной операции, а затем выполните операторы, следующие за телом функции.asyncвнутри функцииreturnЗначение, возвращаемое оператором, станетthenПараметры функции обратного вызова метода.

Разобрать эту булочку

Кровавое дело было возбуждено из-за паровой булочки, а сегодняшняя акция по сбору одежды была вызвана статьей. Тогда давайте вернемся и посмотрим на эту статью «Почему ожидание не работает в forEach».

В статье есть такой кусок кода:


function test() {
  let arr = [3, 2, 1]
  arr.forEach(async item => {
    const res = await fetch(item)
    console.log(res)
  })
  console.log('end')
}

function fetch(x) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(x)
    }, 500 * x)
  })
}

test()

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

Давай, давайте изменим сценарий, все еще вforEachВнутри, но повозитесь с функцией обратного вызова внутри.


function test() {
    let arr = ["a", "b", "c"]
    arr.forEach(async (item,index) => {
      console.log('循环第'+index+'次')
      const res = await fetch(item)
      console.log('res',res)
      const res1 = await fetch1(res);
      console.log('res1',res1)
    })
    console.log('end')
  }
  
  function fetch(x,index) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(x+"经过fetch处理")
      }, 500)
    })
  }
  
  function fetch1(x) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(x+" 经过fetch1处理")
      }, 100)
    })
  }
  
  test()

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

цикл 0
цикл 1
2-й цикл
end
undefined
res a обрабатывается выборкой
res b обрабатывается выборкой
res c обрабатывается выборкой
res1 a обрабатывается fetch и обрабатывается fetch1
res1 b обрабатывается fetch и обрабатывается fetch1
res1 c обрабатывается fetch и обрабатывается fetch1

Уважаемые, можете попробовать, это выход такой. Эй, я пробовал, и это действительно так.

Давайте посмотрим, почему сценарий один не достигает своей цели, а сценарий два достигает своей цели? очень простой,asyncЧто возвращает функция, что возвращаетсяpromise, является асинхронным объектом. а такжеforEachявляется функцией обратного вызова одна за другой, что означает, что эти функции обратного вызова будутвыполнить немедленно, при выполнении наawaitКогда ключевое слово рядом, он вернетpromiseобъект,asyncВнутри функция заморожена, ожидаяawaitПосле выполнения следующего асинхронного выражения выполните его снова.asyncОставшийся код внутри функции. Итак, скрипт one forEach получает кучуpromiseобъект вместоasyncРезультат выполнения внутри функции.asyncфункциональные гарантии являются внутренними для функцииawaitпоследовательность выполнения. Тогда это можно объяснитьasyncсуществуетforEachЭто работает, но сцена не подходит.

Суммировать

На самом деле, неважноasyncещеgeneratorЕсть еще много моментов, которые не набраны.asyncа такжеgeneratorПоявление асинхронной обработки функций — действительно качественный скачок по сравнению с исходной пирамидой функций обратного вызова,promiseС точки зрения несемантики,asyncВполне способна сыграть главную женскую роль.

использованная литература

1. «Повторное изучение JS: почему ожидание не действует в forEach»juejin.cn/post/1

2. «Введение в ECMAScript 6»es6.ruanyifeng.com/#docs/async