Turn, original link juejin.cn/post/684490… In this paper, starting from https://vsnail.cn/static/doc/blog/asyncForEach.html

I happened to see an article that said “await” does not take effect in forEach. I should be familiar with the syntax of async and await in ES6. I used it one or two years ago and knew what it was used for and how to use it. After reading this article, “seventh sense” felt that the title itself seemed to be inappropriate. There was no problem with the content, but when I saw the summary and the remaining comments, I always felt that there should be a misunderstanding. So I want to scratch “his coat” to see what brand it is. Really just look at the sign). After reading several articles describing async and await in detail, I found that it was a mistake to write this article. Because it is too deep, involved too much, like the steamed bread in the infinite, can pull out a bunch of stories; Also like a strength, background female number one, everywhere is play.

You can have everything in this world, if you’re bad enough, and you, you’re not bad enough.

All right, without further ado, let’s get to the point. Let’s pick them up one by one and see what they are.

The async and await ES2017 standards introduce async functions to make asynchronous operations more convenient. OK, let’s see how that works.

async functiongetBookInfo(name){ const baseInfo = await requestBookBaseInfo(name); // The requestBookBaseInfo method sends a request to the background for data. This is an asynchronous method const bookPrice = await requestBookPrice(baseInfo.id); // The requestBookPrice method sends a request to the background for data. This is an asynchronous methodreturn{.. baseInfo,bookPrice}; }Copy the code

In the getBookInfo method, there are two asynchronous functions, and the second one uses the results of the first. If getBookInfo does what we want it to do, then think about it with your little finger and there will be a straightforward conclusion.

After an async function uses an await function internally, an async function followed by an await function can become synchronous.

Assuming that this conclusion is correct, how can async function be implemented to have such magical effects? Does async return the value of the return in the function? Can await only be used with asynchronous functions?

Ok, so with those questions in mind, let’s scroll down and see if it’s A, B, C, D, E, F, G…

In his article on Async functions in Introduction to ECMAScript 6, Ruan da Shen mentions this sentence: “What is async function? In short, it is the syntactic sugar of Generator functions.”

What? Async is a syntactic sugar for Generator? Okay, let’s go grab today’s number two, Generator.

Generator Generator functions are an asynchronous programming solution provided by ES6 with completely different syntactic behavior from traditional functions. — This is also what Nguyen Da Shen said.

As I understand it, Generator means “to generate”, so a Generator function is essentially a Generator, which generates what? An Iterator. Wait, there’s an Iterator. What’s that? Well, let’s put her aside for a moment, because we’re three, so we can’t get in so fast. If you don’t know about woman number three, you can also think of Generator as a state manager. After all, the great poet once said, “when viewed sideways, a mountain becomes a peak.” Now we are just looking at the female no.2 from a different Angle.

Formally, a Generator is just an ordinary function with two distinct features. There is an * between the keyword function and the function name. Second, use yield expressions inside functions to define different states (note that this is why it is also called a state management machine).

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}
Copy the code

The above code defines a Generator function that has three yields inside, meaning that the function has four states (use toilet, wash hands, sit down, and return eat). ChildEatProcess, like any other function, can be called directly. But its return (note here) is not the value of return, but an object, a pointer to the internal state, which is an Iterator.

Besides, the Generator function is similar to the preparation work of our children before dinner. It will not execute itself unless you trigger it. When ch calls the next method, execution starts inside the function, reaches the yield keyword, runs the yield expression, and then stops. Waiting for you to trigger it (next method call)

The yield expression actually provides a way to pause execution because of the iterator object returned by the Generator function that only calls to the next method iterate over the next internal state. The yield expression is the pause flag.

The next method of the traverser object runs as follows.

(1) When a yield expression is encountered, the following operation is paused, and the value immediately following the yield expression is used as the value of the returned object’s value property.

(2) The next time the next method is called, the execution continues until the next yield expression is encountered.

(3) If no new yield expression is encountered, the function is run until the end of the return statement, and the value of the expression following the return statement is used as the value of the returned object’s value property.

(4) If the function does not have a return statement, the value attribute of the returned object is undefined.

Note that the expression following the yield expression is executed only when the next method is called and an internal pointer points to that statement, thus providing JavaScript with manual “Lazy Evaluation” syntactic functionality.

Yield * expressions have an extra asterisk compared to regular yield expressions. Yield expression, used to execute subsequent Generator expressions. This is really hard to express, so let’s take a look at the following code and get a feel for it.


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";
}
letg2 = 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}

letg3 = 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}

Copy the code

From the examples above, you can see that yield simply executes the generator function, that is, gets the iterator generated by the generator function. Yield *, however, executes an internal pointer to the generator function.

Then you can also put the code

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

function* generator_3(){
    yield "a";
    yield* generator_1();
    yield "d"; } // The above code is equivalent tofunction* generator_4(){
    yield "a";
    yield "b";
    yield "c";
    yield "d";
}
Copy the code

Next’s yield expression itself does not return a value, or it always returns undefined. The next method can take an argument that is treated as the return value of the previous yield expression. Notice that this sentence is very important to understand. The important thing to say three times is that the yield expression itself returns no value, the yield expression itself returns no value, and the yield expression itself returns no value.

Iterator being the number three woman in this article, Iterator, let’s just skim it. After all, she is not the author of this article. But don’t underestimate her, this is definitely a heavyweight female protagonist, object traversal, array traversal, pseudo array traversal, deconstruction assignment, extension operator operation, all traversal can be inseparable from her skirt. It’s just a little less of a scene today.

Iterator is an Iterator. It is an interface that provides a unified access mechanism for various data structures. The Iterator interface can be deployed on any data structure to complete traversal (that is, processing all members of the data structure in turn).

The Iterator traverses like this.

(1) Create a pointer object that points to the starting position of the current data structure. That is, the traverser object is essentially a pointer object.

(2) The first call to the next method of the pointer object can point to the first member of the data structure.

(3) The next call to the pointer object points to the second member of the data structure.

(4) Keep calling the next method of the pointer object until it points to the end of the data structure.

Each time the next method is called, information about the current member of the data structure is returned. Specifically, it returns an object containing both the value and done attributes. Where, the value attribute is the value of the current member, and the done attribute is a Boolean value indicating whether the traversal is complete.

I read Iterator’s resume on Generator. It should be clear that the object returned by the Generator is an Iterator of an internal pointer. The Iterator then calls the next method to iterate over all the yield-defined states in the Generator.

Async is a synthetical sugar for a generator. Async is a synthetical sugar for a generator. No hurry. Let’s take our time. Conversely, if it is true that async is the syntax-sugar of a generator, then we can certainly write async effects using generator functions.

After async is disassembled, it can be found that there are actually two points:

The internal asynchronous function becomes synchronous in async, i.e., await the asynchronous expression after execution before continuing. For a generator async is performed automatically, whereas a generator returns an iterator and must call next to execute. OK, then we will implement it one by one according to these two points:

The first one, which is really easy, is that you can implement sequential execution with callbacks, promises, and so on.

The trouble is that we want the Generator function to run automatically instead of calling next manually.

Automatic execution of Generator Thunk is a method of automatic execution of Generator functions.

Long, long ago, there was a debate about “evaluation strategy”, which is when the parameters of a function should be evaluated. Some people feel that the expression should be evaluated only when it is used, so as to avoid unnecessary calculation, equivalent to the name call. Some argue that expressions should be evaluated before they are used, equivalent to passing a value call.

Thunk, on the other hand, is an implementation of a named call, which puts arguments into a temporary function and passes that temporary function into the function body.

The JavaScript language is called by value, and its Thunk function has a different meaning. In the JavaScript language, the Thunk function replaces, instead of an expression, a multi-argument function, with a single-argument function that takes only a callback function as an argument. It seems to be the same thing as keratology.

function readSome(a,b,callBack){
    setTimeout(function(){ callBack && callBack(a+b); }}, 200)let thunkFun = function(fn){
    return function(... args){return function(callBack){
           returnfn.call(this,... args,callBack); }}}let thunk_rs = thunkFun(readSome);

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

Copy the code

What’s the use of the Thunk function, you might ask? Has anything to do with Generator self-execution. Take your time. You take your clothes off and put them on.

function* gen() {/ /... } var g = gen(); var res = g.next();while(! res.done){ console.log(res.value); res = g.next(); }Copy the code

In the code above, the Generator function Gen performs all the steps automatically. However, this is not suitable for asynchronous operations. If the previous step must be completed before the next step can be executed, the above automatic execution is not feasible. This is where the Thunk function comes in handy.

function readSome(a,b,callBack){
    setTimeout(function(){ callBack && callBack(a+b); }}, 200)let thunkFun = function(fn){
    return function(... args){return function(callBack){
           returnfn.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)


Copy the code

This seems to be the perfect auto-execution. Of course, this is not the only way to automate.

From the previous understanding, we know that the principle of async is that Generator functions and self-effectors are wrapped in one function. Hence async is a syntactic sugar for a Generator. The truth is revealed, the original female no. 1 is wearing a vest of female No. 2, but this vest gave the female no. 1 some special ability. Just like Superman has to wear his suit to be superman, to have superpowers.

Again async wearing a vest naturally some places are different, although the internal data are the same. So let’s see what happens when you see the waistcoat.

The improvements of async over Generator are shown in the following four aspects.

(1) Built-in actuators. That’s what we call self-executing.

(2) Better semantics.

(3) wider applicability.

(4) Return the value promise

The most important things are number one and number four. The first point, we all know, don’t say. Fourth, return a Promise and the Generator returns an iterator. This is a very important difference.

In fact, when an async function executes, it will return (return a promise object) as soon as it encounters an await, wait until the asynchronous operation is complete, and then execute the following statement in the function body. The value returned by the return statement inside the async function becomes an argument to the then method callback function.

Analysis that steamed bread for a steamed bread caused a murder, for an article triggered today’s stripping action. Let’s go back to the article “Why await doesn’t work in forEach”.

There is this code in the article:

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(a)Copy the code

This code actually wants to do something, although asynchronous, to display the elements of an array in sorted order. Traversal with forEach does not fulfill this requirement. Hence the title of the article. But girl number one means this pot. I won’t carry it. It’s not that I’m bad, it’s that you didn’t put me in a good script.

Let’s change the script. It’s still inside forEach, but let’s do something inside the callback function.


function test() {
    let arr = ["a"."b"."c"]
    arr.forEach(async (item,index) => {
      console.log('cycle control'+index+'time')
      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(a)Copy the code

In this script, the async function is await two asynchronous expressions. And all asynchronous expressions that are the latter await take the return value of the former await expression as an argument. That is, if async is used in a forEach, then the next asynchronous expression must take the return value of the previous one as an argument. In other words, the desired output should be:

End undefined RES A Fetch process RES B Fetch process RES C FETCH process RES1 A Fetch process Fetch1 Res1 B Fetch process Fetch1 process RES1 C Fetch process Fetch1 process

Guys, you can see if this is the output. Heh heh, I have tried, and it does output like this.

Let’s see why script one doesn’t do what it’s supposed to do, and script two does? It’s very simple. What async returns, it returns a Promise, it’s an asynchronous object. ForEach is one callback function after another, that is, these callback functions will be executed immediately. When executed near an await keyword, a promise object will be returned. Inside the async function is frozen, waiting for the asynchronous expression following the await to complete. The rest of the code inside the async function is executed. Thus, a forEach scenario gets a bunch of promise objects instead of the result of an async function. The async function guarantees the sequential execution of await within the function. Then it can be shown that async is useful in forEach, but the scene is wrong.

To sum up, both Async and Generator still have many points to pick up. The emergence of async and generator is really a qualitative leap in the processing of asynchronous functions. Compared with the original pyramid of callback functions and the non-semantization of promise, async is fully capable of female first.

Im /post/5cb1d5 “Why await doesn’t work in forEach”

2. Introduction to ECMAScript 6 es6.ruanyifeng.com/#docs/async