Detailed explanation of asynchronous Promise and async / wait

1, Why async / wait?

We all know that Promise solution is available. Why should ES7 propose a new async / wait standard?

In fact, the answer is also obvious: Promise has jumped out of the strange circle of asynchronous nesting and expressed more clearly in chain. However, we also found that if there are a large number of asynchronous requests and the process is complex, we will find that then full of screens looks very difficult. The emergence of Async / wait in ES7 is to solve this complex situation.

First, we must understand Promise.

2, Promise introduction

2.1 Promise instance

What is Promise? Many people should know the basic concept? Look directly at the following code (the full text examples are based on setDelaySecond and setDelay functions, please remember):

const setDelay = (millisecond) => {
  return new Promise((resolve, reject)=>{
      if (typeof millisecond != 'number') reject(new Error('Parameter must be number type'));
      setTimeout(()=> {
        resolve(`I delayed ${millisecond}Output after milliseconds`)
      }, millisecond)
  })
}

We encapsulate a Promise in a function and return a Promise, which is more standardized.

You can see that the defined Promise has two parameters, resolve and reject.

  • Resolve: change asynchronous execution from pending (request) to resolve (successful return), which is a function execution return.
  • Reject: as the name suggests, "reject" changes from request to "failure". It is a function that can execute and return a result, but we recommend that you return an error new Error()

.
In the above example, you can reject('return a string ') and return whatever you want, but we still recommend returning an Error object, which is more clear that it is "failed", which is more standardized.

2.2 then and catch of promise

We get our return value through Promise's prototype method then:

setDelay(3000)
.then((result)=>{
    console.log(result) // Output "I output after a delay of 2000 milliseconds"
})

Output the following value: "I output after a delay of 2000 milliseconds".

What if something goes wrong? Then use catch to capture:

setDelay('I'm a string')
.then((result)=>{
    console.log(result) // I'm not going in
})
.catch((err)=>{
    console.log(err) // Output error: 'parameter must be of type number'
})

Isn't it simple? OK, now I'll add a little difficulty. What happens if multiple promises are executed?

2.3 Promise interdependence

We are writing a Promise:

const setDelaySecond = (seconds) => {
  return new Promise((resolve, reject)=>{
      if (typeof seconds != 'number' || seconds > 10) reject(new Error('Parameter must be number Type and less than or equal to 10'));
      setTimeout(()=> {
        console.log(`First setDelaySeconds Function output, delayed ${seconds}Seconds, a total delay is required ${seconds+2}second`)
        resolve(setDelay(2000)) // This depends on the previous Promise
      }, seconds * 1000)
  })
}

What happens when the next resolve that needs to be relied on returns another Promise? Let's do the following:

setDelaySecond(3).then((result)=>{
  console.log(result)
}).catch((err)=>{
  console.log(err);
})

You will find that the result is executed first: "setDelaySeconds output first, delayed by 2 seconds, a total of 5 seconds"

Then execute the resolve of setDelay: "I output after a delay of 2000 milliseconds". Indeed, the purpose of sequential implementation has been achieved.

Some people say that I don't want such high coupling. I want to execute setDelay function first and then setDelaySecond, but I don't want to use the above writing method. Is it OK? The answer is of course.

2.4 Promise chain writing

First rewrite setDelaySecond to reject dependency and reduce coupling

const setDelaySecond = (seconds) => {
  return new Promise((resolve, reject)=>{
      if (typeof seconds != 'number' || seconds > 10) reject(new Error('Parameter must be number Type and less than or equal to 10'));
      setTimeout(()=> {
        resolve(`I delayed ${seconds}Seconds later, the output is the second function`)
      }, seconds * 1000)
  })
}

Execute setDelay first. When setDelaySecond is executed, you only need to return the next Promise in the result of the first then, and you can write it all the time in a chain, which is equivalent to executing in sequence:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(3)
})
.then((result)=>{
  console.log('I went to the second step');
  console.log(result);
}).catch((err)=>{
  console.log(err);
})

It is found that it has indeed reached a gratifying chain (finally separated from the bitter sea of asynchronous nesting, crying). You can see that then's chain writing method is very beautiful.

2.5 points needing attention in chain writing

One thing must be mentioned here:

The essence of then chaining is to pass down and return a new Promise, that is, then receives the Promise returned in the previous step in the next step. Understanding this is very important for the following details!!

It's not that simple. We can see that there are two parameters (both callbacks) in the return of then:

The first callback is the callback of resolve, that is, the first parameter is used most, and the value of Promise successful resolve in the previous step is obtained.
The second callback is the reject callback, which is not used much, but please don't write it wrong. It is usually the error of the previous one. What is the difference between this error handling and catch and what should be paid attention to?
We modify the above code:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(20)
})
.then((result)=>{
  console.log('I went to the second step');
  console.log(result);
}, (_err)=> {
  console.log('I made a mistake. I came here to catch the error, but I didn't go through it catch Yes');
})
.then((result)=>{
  console.log('I still continue to execute!!!!')
})
.catch((err)=>{
  console.log(err);
})

You can see that the output result is: it goes into the second parameter (reject) of then, and most importantly! It doesn't go through catch anymore.

Then we move the catch up and write it before then error handling:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(20)
})
.catch((err)=>{ // Move it up
  console.log(err); // Here, catch the last error that returned Promise
})
.then((result)=>{
  console.log('I went to the second step');
  console.log(result);
}, (_err)=> {
  console.log('I made a mistake, but because catch In front of me, so the error has long been caught. I have no error');
})
.then((result)=>{
  console.log('I still continue to execute!!!!')
})

You can see that there are no errors after catch ing.

It can be concluded that:

The catch method is used to capture the errors of the whole chain method, and the second parameter of then is for the Promise returned by the previous one.
Priority of the two: it depends on who is in front of the chain writing method. The first one catches the error, and then there is no error to catch. The priority in front of the chain is large, and neither of them is a break. You can continue to perform subsequent operations without being affected.

2.6 error handling of chain writing

The problem that the second callback (reject) of the three callbacks in then will conflict with catch has been written above. When we actually write, the method of parameter capture is basically less written, and the method of catch will be used more.

Since there are many promises, do I need to write many catch es?

Of course, the answer is: no!, There is no such troublesome writing method. You only need to catch at the end, because the error handling of chain writing has the characteristic of "bubbling". If there is a problem in any link in the chain, it will be caught, and the code behind a link will not be executed.

Having said that, what happens when we move the catch to the first chained return? Look at the following code:

setDelay('2000')
.then((result)=>{
  console.log('The first step is complete');
  console.log(result)
  return setDelaySecond(3)
})
.catch((err)=>{ // Here, move to the first chain. It is found that the upper chain is not executed, and the lower chain continues to execute
  console.log(err);
})
.then((result)=>{
  console.log('The second step is complete');
  console.log(result);
})

Surprised to find that the chain continues!! The output is as follows (undefined because the previous then did not return a Promise):

Here's the point! Knock on the blackboard!! The catch in the chain is not the end!! After catch, if there are then, they will continue to go down! If you don't believe it, you can add a few then after the last example of the first catch, and you will find that it won't jump out of the chain execution.

If setDelay, setDelay1 and setdelaysecond are executed sequentially, the flow chart can be summarized as follows according to the above logic:

catch is just a chain expression that captures errors, not a break!

Therefore, the position of catch is also very particular. It is generally placed at the end of some important programs that must catch** Once an error occurs in these important programs, they will immediately skip the operation of other subsequent programs and directly execute to the nearest catch code block, but it will not affect the subsequent operation of catch!!!!

At this point, we have to find a Promise finally newly introduced in the ES2018 standard, which means that the operation must be performed by default after the catch. There is not much to expand here. For details, please refer to: Promise finally

2.7 Promise chain intermediate wants to return user-defined values

In fact, it is very simple. Use Promise's prototype method to resolve:

setDelay(2000).then((result)=>{
  console.log('The first step is complete');
  console.log(result);
  let message = 'This is the value I want to deal with myself'; 
  return Promise.resolve(message) // This returns the value I want to process in the next stage
})
.then((result)=>{
  console.log('The second step is complete');
  console.log(result); // Here we get the return value of the previous stage
  //return Promise.resolve('You can continue to return here ')
})
.catch((err)=>{
  console.log(err);
})

2.8 how to jump out or stop Promise chain

Different from the general function break mode, if you operate like this: func() then(). then(). then(). In the catch () mode, you want to jump out of the chain at the first then, and you don't want to execute the latter, which is different from the general break;return null;return false and other operations. It can be said that how to stop the Promise chain is a major difficulty and the most complex part of the whole Promise.

1. Using chain thinking, if we refuse to drop a chain, isn't it equivalent to directly jumping to the catch module?

Can we directly "refuse" to stop?

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(1)
})
.then((result)=>{
  console.log('I went to the second step');
  console.log(result);
  console.log('I jumped out of the loop');
  return Promise.reject('Jump out of loop information') // Here, a reject is returned and the loop is actively jumped out
})
.then((result)=>{
  console.log('I don't execute');
})
.catch((mes)=>{
  console.dir(mes)
  console.log('I jumped out');
})

But it's easy to see the disadvantages: sometimes you're not sure whether you jump out because of an error or take the initiative, so we can add a flag bit:

return Promise.reject({
    isNotErrorExpection: true // Add a flag bit to the returned place to judge whether it is an error type. If not, it indicates that it can actively jump out of the loop
}) 

Or judge whether the output type of catch belongs to the error object according to the above code. If yes, it indicates that it is an error, and if not, it indicates that it jumps out actively. You can choose by yourself (that's why you want to output new Error('error message ') when the error reject is unified. Specification!)

Of course, you can also throw an error and jump out:

throw new Error('error message') // If you jump out directly, you can't judge whether it is the wrong object

2. Sometimes we have this requirement: catch is placed in the middle (not at the end), and at the same time we don't want to execute the code behind catch, that is, chain absolute suspension. What should we do?

Let's look at this Code:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(1)
})
.then((result)=>{
  console.log('I went to the second step');
  console.log(result);
  console.log('I jumped out of the loop');
  return Promise.reject('Jump out of loop information') // Here, the Promise prototype method is directly called to return a reject, which actively jumps out of the loop
})
.then((result)=>{
  console.log('I don't execute');
})
.catch((mes)=>{
  console.dir(mes)
  console.log('I jumped out');
})
.then((res)=>{
    console.log('I didn't want to do it, but I did'); // The problem here is that the above-mentioned termination method will treat the symptoms rather than the root causes.
})

At this time, the last step then is still implemented. In fact, the whole chain does not essentially jump out. What should we do?

Knock on the blackboard!! Here's the point! We can see from Promise/A + specification:

A promise must be in one of three states: pending, fulfilled, or rejected.

Promise actually has three states: pending, resolved and rejected. We have been discussing the two states of resolve and rejected. Have we ignored the pending state? As the name suggests, the pending state is the state in the request, the successful request is resolve, and the failure is reject. In fact, it is an intermediate transition state.

As we discussed above, the next level of then actually gets the Promise object returned by the previous level, that is, the original Promise object is consistent with the state of the new object. So here's the point. If you want to terminate at this level, do you want it to be pending forever, and the subsequent operations will be gone? Has this goal been achieved?? If you have any questions, please refer to Promise/A + specification.

Let's look directly at the code:

setDelay(2000)
.then((result)=>{
  console.log(result)
  console.log('I went to the first step');
  return setDelaySecond(1)
})
.then((result)=>{
  console.log(result);
  console.log('I jumped out of the loop');
  // return Promise.reject('Information jumping out of the loop ')
  // That's the point
  return new Promise(()=>{console.log('Subsequent will not be executed')}) // If a new Promise is returned here and there is no resolve or reject, it will always be in the pending state. If it is not returned, this state will always be maintained and the Promise will be interrupted
})
.then((result)=>{
  console.log('I don't execute');
})
.catch((mes)=>{
  console.dir(mes)
  console.log('I jumped out');
})
.then((res)=>{
  console.log('I won't do it either')
})

This solves the problem that the Promise chain cannot be completely terminated due to the jump out of the above error.

But! There is also a problem that may lead to potential memory leakage, because we know that the Promise that has been in the pending state will always be in the suspended state, and we don't know the details of the browser's mechanism. It doesn't matter with general web pages, but a large number of complex pending States are bound to lead to memory leakage, No specific tests have been conducted, and follow-up tests may be conducted in the future (this is not recommended in nodeJS or webapp), and it is difficult for me to find the answer through query. This article can be recommended: Start with how to stop Promise chain . It may help you how to do in this situation.

Of course, generally, there will be no leakage, but there is such a risk, and the inability to cancel Promise has always been its pain point. The above two wonderful cancellation methods should be used in specific situations.

2.9 Promise.all

In fact, these methods are simple, which is an abbreviation for concatenating all the Promise execution you need. For details, please refer to Ruan Yifeng's es6promise al l tutorial.

Here is my last code example

Promise.all([setDelay(1000), setDelaySecond(1)]).then(result=>{
  console.log(result);
})
.catch(err=>{
  console.log(err);
})

// Output ["I output after a delay of 1000 milliseconds", "I output after a delay of 1 second. Note that the unit is second"]

The output is an array, which is equivalent to executing Promise in the all method in parallel. Note that it is parallel.
It is equivalent to two promises starting to execute at the same time and returning values at the same time, instead of executing the first and then the second. If you want to execute serially, please refer to the circular Promise circular serial (Section 4.2) I write later.

Then save the value of resolve in the array for output. Similarly, there are Promise.race I won't repeat it here.

3, Introduction to Async/await

3.1 Promise based Async/await

What is async/await? It can be summarized as one sentence: async/await is a pair of good friends. They are indispensable. Their birth serves Promise. It can be said that async/awai is Promise's father, the evolutionary version. Why do you say that? Listen to me carefully.

Why should async/await exist?

As mentioned earlier, in order to solve the problem of a large number of complex and unreadable Promise asynchrony, an improved version of Promise appears.

These two friends must appear at the same time. Neither of them is indispensable. Let's talk about Async first:

async function process() {
}

As can be seen from the above, async must declare a function. Don't declare anything else. In that case, await will ignore you (report an error).

This statement is also wrong!

const async demo =  function () {} // error

Must be followed by function. Next, let's talk about its brother await.

As mentioned above, it must be a function, so await must be used inside the function declared by async, otherwise an error will be reported.

Even if you write that, it's wrong.

let data = 'data'
demo  = async function () {
    const test = function () {
        await data
    }
}

It must be an immediate system (the scope chain cannot be inter generational), and an error will be reported: uncaught syntax error: await is only valid in async function.

After talking about the basic norms, let's move on to their essence.

3.2 essence of Async

Knock on the blackboard!!! Very important! The return of the async declared function is essentially a Promise.

What do you mean? That is, as long as you declare that this function is async, no matter how you handle it internally, its return must be a Promise.

Look at the following examples:

(async function () {
    return 'I am Promise'
})()
// Return is Promise
//Promise {< resolved >: "I'm promise"}

You will find that the return is this: Promise {< resolved >: "I am Promise"}.

Automatically resolve to Promise Resolve ('I am Promise ');

Equivalent to:

(async function () {
    return Promise.resolve('I am Promise');
})()

So imagine the return of a general function. Get the return value and change your original thinking! You can get the return value as follows:

const demo = async function () {
    return Promise.resolve('I am Promise');
    // Equivalent to return 'I am Promise'
    // Equivalent to return new Promise ((resolve, reject) = > {resolve ('I am Promise ')})
}
demo.then(result=>{
    console.log(result) // Get the return value here
})

The above three writing methods are OK. It depends on the notes. The details are written in it!! Treat the return value of async like Promise!!!

OK, next we'll see what await's for

3.3 essence and examples of await

The essence of await is that it can provide syntax sugar equivalent to the ability to wait for asynchronous return.

At first glance, this sentence is very awkward. OK, it's not urgent. Let's start with an example:

const demo = async ()=>{
    let result = await new Promise((resolve, reject) => {
      setTimeout(()=>{
        resolve('I was delayed by a second')
      }, 1000)
    });
    console.log('Because the above program has not been executed, I will not execute "wait a minute"');
}
// The return of demo is treated as Promise
demo().then(result=>{
  console.log('output',result);
})

Await as its name implies is to wait for a while. As long as the function declared by await has not returned, the following program will not be executed!!!. This is literally waiting for a while (waiting for return before execution).

Then you go here to test, and you will find that the output is this: the output is undefined. Why? This is also a place I want to emphasize!!!

You didn't declare the return in the demo function. Where did you get then? So the correct wording is as follows:

const demo = async ()=>{
    let result = await new Promise((resolve, reject) => {
      setTimeout(()=>{
        resolve('I was delayed by a second')
      }, 1000)
    });
    console.log('Because the above program has not been executed, I will not execute "wait a minute"');
    return result;
}
// The return of demo is treated as Promise
demo().then(result=>{
  console.log('output',result); // I delayed the output by one second
})

I recommend that you take then with you and be more standardized. Of course, it's ok if you don't return. The demo will be executed as usual. The following method is written without return value:

const demo = async ()=>{
    let result = await new Promise((resolve, reject) => {
      setTimeout(()=>{
        resolve('I was delayed by a second')
      }, 1000)
    });
    console.log('Because the above program has not been executed, I will not execute "wait a minute"');
}
demo();

Therefore, it can be found that as long as you declare the asynchronous return with await, you must "wait" until there is a return value before the code continues to execute.

Is that true? You can run this Code:

const demo = async ()=>{
    let result = await setTimeout(()=>{
      console.log('I was delayed by a second');
    }, 1000)
    console.log('Because the above program has not been executed, I will not execute "wait a minute"');
    return result
}
demo().then(result=>{
  console.log('output',result);
})

You will find that the output is as follows:

Because the above program has not been executed, I will not execute "wait a minute"
Output 1
I was delayed by a second

Strange, no await? SetTimeout is asynchronous. What's the problem? The problem is that setTimeout is asynchronous, but not Promise! It doesn't play the role of "waiting for a while".

Therefore, a more accurate statement should be that Promise declared with await returns asynchronously. You must "wait" until there is a return value before the code continues to execute.

Remember that * * await is waiting for an asynchronous return of Promise**
Of course, this waiting effect only exists in the case of "asynchronous". Can await be used to declare the value transfer in general?

The fact is, of course:

const demo = async ()=>{
    let message = 'I'm declaring the value'
    let result = await message;
    console.log(result); 
    console.log('Because the above program has not been executed, I will not execute "wait a minute"');
    return result
}
demo().then(result=>{
  console.log('output',result);
})

Output:

I'm declaring the value
Because the above program has not been executed, I will not execute "wait a minute"
The output is the declared value

Just note that the execution of then is always the last.

3.4 async/await advantage practice

Now let's look at the actual combat:

const setDelay = (millisecond) => {
  return new Promise((resolve, reject)=>{
      if (typeof millisecond != 'number') reject(new Error('Parameter must be number type'));
      setTimeout(()=> {
        resolve(`I delayed ${millisecond}Output after milliseconds`)
      }, millisecond)
  })
}
const setDelaySecond = (seconds) => {
  return new Promise((resolve, reject)=>{
      if (typeof seconds != 'number' || seconds > 10) reject(new Error('Parameter must be number Type and less than or equal to 10'));
      setTimeout(()=> {
        resolve(`I delayed ${seconds}Output after seconds. Note that the unit is seconds`)
      }, seconds * 1000)
  })
}

For example, the above two delay functions (written above), for example, I want to delay 1 second first, 2 seconds later, and then 1 second later, and finally output "complete". This process, if written in then, is probably like this (nested hell, turn right when you go out and don't send it):

setDelay(1000)
.then(result=>{
    console.log(result);
    return setDelaySecond(2)
})
.then(result=>{
    console.log(result);
    return setDelay(1000)
})
.then(result=>{
    console.log(result);
    console.log('complete')
})
.catch(err=>{
    console.log(err);
})

At first glance, isn't it very cumbersome? If you have more logic, you may feel more tired. Now let's try async/await

(async ()=>{
  const result = await setDelay(1000);
  console.log(result);
  console.log(await setDelaySecond(2));
  console.log(await setDelay(1000));
  console.log('It's done');
})()

Look! Is there no redundant long chain code, and the semantics is very clear and comfortable? Then you must also find that the catch above is not implemented in async? Next, let's analyze how async/await handles errors?

3.5 async/await error handling

Because the async function returns a Promise, we can catch the error outside.

const demo = async ()=>{
  const result = await setDelay(1000);
  console.log(result);
  console.log(await setDelaySecond(2));
  console.log(await setDelay(1000));
  console.log('It's done');
}
demo().catch(err=>{
    console.log(err);
})

Catch the error in the catch of async function and treat it as a poise. At the same time, if you don't want to use this method, you can use try Catch statement:

(async ()=>{
  try{
    const result = await setDelay(1000);
    console.log(result);
    console.log(await setDelaySecond(2));
    console.log(await setDelay(1000));
    console.log('It's done');
  } catch (e) {
    console.log(e); // Errors are caught here
  }
})()

Of course, you don't need to catch outside at this time.

Usually our try There won't be too many catches, but there are the most. If there are too many catches, your code must need to be refactored, and it must not be written very well. Another thing is try Catch is usually used only when needed. Sometimes it is not necessary to write catch errors.

Someone will ask, I try It seems that catch can only wrap code blocks. If I need to split them and handle them separately, I don't want to crash the whole process because of a single error, so do I have to write a pile of try Catch? I'm just uncomfortable. I just don't want to write try What about catch? Here is a good solution for reference only:

We know that await must be followed by a Promise. Can it be written like this?

(async ()=>{
  const result = await setDelay(1000).catch(err=>{
      console.log(err)
  });
  console.log(result);
  const result1 = await setDelaySecond(12).catch(err=>{
      console.log(err)
  })
  console.log(result1);
  console.log(await setDelay(1000));
  console.log('It's done');
})()

Output this way:

I delayed the output after 1000 milliseconds
Error: Parameter must be number Type and less than or equal to 10
    at Promise (test4.html:19)
    at new Promise (<anonymous>)
    at setDelaySecond (test4.html:18)
    at test4.html:56
undefined
 I delayed the output after 1000 milliseconds
 It's done

Is it great that even if there is an error, it will not affect the subsequent operation? Of course not. You said the code was ugly. It was messy and awkward. await followed catch. Then we can improve and encapsulate the code function to extract errors:

// to function
function to(promise) {
   return promise.then(data => {
      return [null, data];
   })
   .catch(err => [err]); // Return writing of es6
}

The returned is an array. The first is an error and the second is an asynchronous result. Use the following:

(async ()=>{
   // The writing method of es6 returns an array (you can change back to the writing method of es5. If you are not used to it), the first is the error information, and the second is the asynchronous return data of then. Here, please note that repeated variable declarations may cause problems (for example, global, if you use let and const, please change the variable name).
  [err, result] = await to(setDelay(1000)) 
   // If err exists, there is an error. If you don't want to continue, an error will be thrown
  if (err) throw new Error('An error occurred and I don't want to execute it');
  console.log(result);
  [err, result1] = await to(setDelaySecond(12))
   // If you want to execute, don't throw an error
  if (err) console.log('An error occurred and I want to continue', err);
  console.log(result1);
  console.log(await setDelay(1000));
  console.log('It's done');
})()

3.6 async/await interrupt (program termination)

First of all, we should make it clear that promise itself cannot be suspended. Promise itself is just a state machine, It stores three states (pending, resolved and rejected). Once a request is sent, it must be closed-loop and cannot be cancelled. The previous pending state is only a state of pending requests, not cancellation. Generally, this situation will not occur, but is only used to temporarily suspend the chain process.

The essence of interrupt (termination) is only suspended in the chain, not the essence of canceling the Promise request. That can't be done, and Promise doesn't have the state of canceling.

Different from Promise's chain writing method, it is very easy to interrupt the program in async/await, because the semantics is very obvious. In fact, it is the same as the general function writing method. When you want to interrupt, you can directly return a value, null, empty and false. See example:

let count = 6;
const demo = async ()=>{
  const result = await setDelay(1000);
  console.log(result);
  const result1 = await setDelaySecond(count);
  console.log(result1);
  if (count > 5) {
      return 'I quit. The following is not going on';
    // return; 
    // return false; //  These writing methods are OK
    // return null;
  }
  console.log(await setDelay(1000));
  console.log('It's done');
};
demo().then(result=>{
  console.log(result);
})
.catch(err=>{
  console.log(err);
})

The essence is to return a Promise directly, which is equivalent to return Promise Resolve ('I quit and don't proceed below '). Of course, you can also return a "reject": return Promise Reject (new error ('reject ') will be entered into the error message.

The essence of async function is to return a Promise!

4, Points needing attention in actual combat

We often use the above two methods, or mix them. Sometimes we encounter some situations. Here are some examples to illustrate:

4.1 then writing method of promise to obtain data (serial)

Parallel, needless to say, is very simple. You can send a request directly in a loop or use promise all. If we need to serially loop a request, what should we do?

We need to implement a program that delays the output value by 1 second in turn, with a total of 5 seconds. First, Promise loop, which is relatively troublesome:

We often make mistakes! That is, we don't pay attention to the influence of function name and function execution on the program
Not to mention loops, let's take a wrong example. Now there is a delay function

const setDelay = (millisecond) => {
  return new Promise((resolve, reject)=>{
      if (typeof millisecond != 'number') reject(new Error('Parameter must be number type'));
      setTimeout(()=> {
        resolve(`I delayed ${millisecond}Output after milliseconds`)
      }, millisecond)
  })
}

We want to achieve: "the Promise function with a one second delay is executed serially in a loop". The expected result should be: I output it every other second after a delay of 1000 milliseconds, which has been cycled for 3 times in total. We take it for granted to write the following chain:

arr = [setDelay(1000), setDelay(1000), setDelay(1000)]
arr[0]
.then(result=>{
  console.log(result)
  return arr[1]
})
.then(result=>{
  console.log(result)
  return arr[2]
})
.then(result=>{
  console.log(result)
})

But unfortunately, you find that the output is parallel!!! In other words, three values are output at one time in one second!. So what is this? It's actually very simple... When you add setDelay(1000) directly to the array, it has actually been executed. Pay attention to your execution statement (1000)

In fact, this is the foundation and the feature of the language. Many careless people (or those who don't study JS well) will think that this will add the function to the array, but they don't know that the function has been executed once.

So what are the consequences? That is to say, each Promise state saved in the array is the state in which the resolve is completed. Then you directly return arr[1] by chain call later. In fact, you do not request, but immediately return a resolve state. So you will find that the program is equivalent to parallel, and there is no sequential call.

So what is the solution? Direct function names are used to store functions (Promise is not executed)

Let's change the procedure as follows:

arr = [setDelay, setDelay, setDelay]
arr[0](1000)
.then(result=>{
  console.log(result)
  return arr[1](1000)
})
.then(result=>{
  console.log(result)
  return arr[2](1000)
})
.then(result=>{
  console.log(result)
})

The above is equivalent to pre storing Promise in an array and executing it when you need to call it. Of course, you can also store it in the form of closure and execute it when it needs to be called.

4.2 Promise loop for obtaining data (serial)

The above writing method is not elegant. More than once, it will become GG. Why mention the then above is actually to pave the way for the following for loop.

The above procedure is rewritten according to the law:

arr = [setDelay, setDelay, setDelay]
var temp
temp = arr[0](1000)
for (let i = 1; i <= arr.length; i++) {
    if (i == arr.length) {
      temp.then(result=>{
        console.log('It's done');
      })
      break;
    }
    temp = temp.then((result)=>{
        console.log(result);
        return arr[i-1](1000)
    });
}

Error handling can insert try in the for loop Catch, or at each of your loop points then().catch() and () are feasible. If you want to extract it as a public method, you can rewrite it again and use recursion:

First, you need to close your Promise program

function timeout(millisecond) {
  return ()=> {
    return setDelay(millisecond);
  }
}

What will happen if you don't close? Without closures, your Promise will be executed immediately after you pass in the parameter values, resulting in state changes. If you use closures, your Promise will be saved until you need to call it. And the biggest advantage is that you can pass in the parameters you need in advance.

Overwrite array:

arr = [timeout(2000), timeout(1000), timeout(1000)]

The extract method, Promise array, is passed in as a parameter:

const syncPromise = function (arr) {
  const _syncLoop = function (count) {
    if (count === arr.length - 1) { // If you are the last one, return directly
      return arr[count]()
    }
    return arr[count]().then((result)=>{
      console.log(result);
      return _syncLoop(count+1) // Recursive call array subscript
    });
  }
  return _syncLoop(0);
}

use:

syncPromise(arr).then(result=>{
  console.log(result);
  console.log('It's done');
})
// Or add a method to the Promise class
Promise.syncAll = function syncAll(){
  return syncPromise
}// It can be used directly in the future
Promise.syncAll(arr).then(result=>{
  console.log(result);
  console.log('It's done');
})

In addition, the great God summarized the expression of reduce, which is actually a process of iterating arrays:

const p = arr.reduce((total, current)=>{
    return total.then((result)=>{
        console.log(result);
        return current()
    })
}, Promise.resolve('Program start'))
p.then((result)=>{
    console.log('It's over', result);
})

Are feasible in Promise's circular field.

4.3 async/await loop for obtaining data (serial)

Now let's introduce the awesome async/await actual combat. Do you want to see if you vomit the above code? Indeed, I also think it's very troublesome. What can be improved if you use async/await? This is what it means:

Simulate the loop of the above code:

(async ()=>{
    arr = [timeout(2000), timeout(1000), timeout(1000)]
    for (var i=0; i < arr.length; i++) {
        result = await arr[i]();
        console.log(result);
    }
})()

. . . That's it? yes... It's over, isn't it particularly convenient!!!! Semantics is also very obvious!! In order to keep the style consistent with the above, no error handling is added, so remember to add your try Catch statement to catch errors.

4, Postscript

I've always wanted to summarize Promise and async/await. I may not summarize enough in many places. I've tried my best to expand the space. There are new knowledge points and summary points that may be updated later (to be continued), but the introduction is basically enough.

We often say that the emergence of async/await eliminated Promise, which can be said to be a big mistake. On the contrary, it is precisely because of Promise that there is an improved version of async/await. From the above analysis, we can see that the two complement each other and are indispensable.

If you want to learn async/await well, you must first be proficient in Promise. The two are inseparable. If you have different opinions and improvements, welcome guidance!

Front end Xiaobai, let's communicate with each other, peace!

Transferred from: https://segmentfault.com/a/1190000016788484
Record learning, use, invasion and deletion

Keywords: Javascript Front-end Vue.js

Added by Pyrite on Wed, 22 Dec 2021 12:37:25 +0200