Home  >  Article  >  Web Front-end  >  Detailed explanation of ES6's async+await synchronization/asynchronous solution

Detailed explanation of ES6's async+await synchronization/asynchronous solution

巴扎黑
巴扎黑Original
2017-09-20 09:18:012268browse

This article mainly introduces the detailed explanation of ES6's async+await synchronization/asynchronous solution. This article uses the most concise way to unblock async + await. Those who are interested can learn more

Asynchronous programming has always been JavaScript programming major matters. Regarding asynchronous solutions, ES6 first saw the emergence of Promise based on state management, then the Generator function + co function, and then the ES7 async + await solution.

This article strives to unblock async + await in the most concise way.

Several scenarios of asynchronous programming

Let’s start with a common question: How to print the iteration sequence asynchronously in a for loop?

We can easily think of using closures or the let block-level scope specified in ES6 to answer this question.


for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val),100);
}
// => 预期结果依次为:1, 2, 3, 4

What is described here is an asynchronous event that occurs evenly, and they are queued in the asynchronous queue in a predetermined order waiting for execution.

If asynchrony does not occur evenly, then the order in which they are registered in the asynchronous queue is out of order.


for (let val of [1, 2, 3, 4]) {
  setTimeout(() => console.log(val), 100 * Math.random());
}
// => 实际结果是随机的,依次为:4, 2, 3, 1

The returned results are out of order and uncontrollable. This is the most realistic asynchronous. But another situation is that in a loop, what should you do if you want the previous asynchronous execution to be completed and the next asynchronous execution to be executed again?


for (let val of ['a', 'b', 'c', 'd']) {
  // a 执行完后,进入下一个循环
  // 执行 b,依此类推
}

Isn’t this just multiple asynchronous “serials”!

The method of nesting asynchronous operations in the callback and then calling back solves this problem! Alternatively, using Promise + then() to nest layers can also solve the problem. However, if you insist on writing this nesting method in a loop, I'm afraid it will take a lot of trouble. I wonder, is there a better way?

Asynchronous synchronization solution

Just imagine, if you want to send a batch of data to the server, only the previous batch is sent successfully (that is, the server returns a successful response), Only then will the next batch of data be sent, otherwise the sending will be terminated. This is a typical example of "interdependent asynchronous operations in a for loop".

Obviously, this "serial" asynchronous can actually be regarded as synchronization. It takes more time than out-of-order asynchronous. Logically speaking, we want the program to execute asynchronously, just to "skip" blocking and spend less time. But on the contrary, if we need a series of asynchronous "serials", how should we program well?

For this "serial" asynchronous, ES6 can easily solve this problem.


async function task () {
  for (let val of [1, 2, 3, 4]) {
    // await 是要等待响应的
    let result = await send(val);
    if (!result) {
      break;
    }
  }
}
task();

Literally, it is this cycle. When the results are obtained, the next cycle will be carried out. Therefore, the loop is paused ("stuck") every time it executes until the loop ends. This coding implementation effectively eliminates the problem of nested "callback hell" and reduces cognitive difficulty.

This is the solution for synchronizing asynchronous problems. Regarding this solution, if Promise mainly solves the problem of asynchronous callbacks, then async + await mainly solves the problem of synchronizing asynchronous problems and reducing the cognitive burden of asynchronous programming.

async + await "Different on the outside but the same on the inside"

When I came into contact with this API earlier, I looked at the cumbersome documentation and thought that async + await was mainly used to solve the problem. Asynchronous issues are synchronous.

actually not. As can be seen from the above example: the async keyword declares an asynchronous function. There is a line of await statements in the body of this asynchronous function, which notifies that the behavior is executed synchronously, and the adjacent codes above and below are executed line by line in sequence.

Translate this formal thing again, it is:

1. After the async function is executed, a promise object is always returned.
2. The line of statement where await is located is Synchronous

Among them, 1 shows that from the outside, the task method returns a Promise object after execution. Because it returns a Promise, it can be understood that the task is an asynchronous method. There is no doubt that it is used like this:


task().then((val) => {alert(val)})
   .then((val) => {alert(val)})

2 illustrates that within the task function, asynchronous has been "cut" into synchronization. The whole thing is just a function that takes a little time to execute.

Comprehensive 1 and 2, from a formal point of view, "the task as a whole is an asynchronous function, and the entire internal part is synchronous", which is referred to as "different on the outside and the same on the inside".

The whole is an asynchronous function, which is not difficult to understand. In terms of implementation, we might as well reverse it. At the language level, when the async keyword is called, a promise is forced to be added at the end of the function execution. The response:


async fn () {
  let result;
  // ...
  //末尾返回 promise
  return isPromise(result)? 
      result : Promise.resolve(undefined);
}

is internally synchronous How is it done? In fact, the await call causes the following statements (functions) to be executed recursively. It will not be resolved until the result is obtained and its status is changed. Only after the resolve is resolved, the await line of code will be considered completed and will continue. Execute to the next line. Therefore, although there is a large for loop outside, the entire for loop is serialized in sequence.

Therefore, it is not difficult to understand the meaning of async + await just from the appearance of the above framework. It's just that simple to use, but Promise is a basic piece that must be mastered.

秉承本次《重读 ES6》系列的原则,不过多追求理解细节和具体实现过程。我们继续巩固一下这个 “形式化” 的理解。

async + await 的进一步理解

有这样的一个异步操作 longTimeTask,已经用 Promise 进行了包装。借助该函数进行一系列验证。


const longTimeTask = function (time) {
 return new Promise((resolve, reject) => {
  setTimeout(()=>{
   console.log(`等了 ${time||'xx'} 年,终于回信了`);
   resolve({'msg': 'task done'});
  }, time||1000)
 })
}

async 函数的执行情况

如果,想查看 async exec1 函数的返回结果,以及 await 命令的执行结果:


const exec1 = async function () {
 let result = await longTimeTask();
 console.log('result after long time ===>', result);
}
// 查看函数内部执行顺序
exec1();
// => 等了 xx 年,终于回信了
// => result after long time ===> Object {msg: "task done"}

//查看函数总体返回值
console.log(exec1());
// => Promise {[[PromiseStatus]]: "pending",...}
// => 同上

以上 2 步执行,清晰的证明了 exec1 函数体内是同步、逐行逐行执行的,即先执行完异步操作,然后进行 console.log() 打印。而 exec1() 的执行结果就直接是一个 Promise,因为它最先会蹦出来一串 Promise ...,然后才是 exec1 函数的内部执行日志。

因此,所有验证,完全符合 整体是一个异步函数,内部整个是同步的 的总结。

await 如何执行其后语句?

回到 await ,看看它是如何执行其后边的语句的。假设:让 longTimeTask() 后边直接带 then() 回调,分两种情况:

1)then() 中不再返回任何东西
2) then() 中继续手动返回另一个 promise


const exec2 = async function () {
 let result = await longTimeTask().then((res) => {
  console.log('then ===>', res.msg);
  res.msg = `${res.msg} then refrash message`;
  // 注释掉这条 return 或 手动返回一个 promise
  return Promise.resolve(res);
 });
 console.log('result after await ===>', result.msg);
}
exec2();
// => 情况一 TypeError: Cannot read property 'msg' of undefined
// => 情况二 正常

首先,longTimeTask() 加上再多得 then() 回调,也不过是放在了它的回调列队 queue 里了。也就是说,await 命令之后始终是一条 表达式语句,只不过上述代码书写方式比较让人迷惑。(比较好的实践建议是,将 longTimeTask 方法身后的 then() 移入 longTimeTask 函数体封装起来)

其次,手动返回另一个 promise 和什么也不返回,关系到 longTimeTask() 方法最终 resolve 出去的内容不一样。换句话说,await 命令会提取其后边的promise 的 resolve 结果,进而直接导致 result 的不同。

值得强调的是,await 命令只认 resolve 结果,对 reject 结果报错。不妨用以下的 return 语句替换上述 return 进行验证。


return Promise.reject(res);

最后

其实,关于异步编程还有很多可以梳理的,比如跨模块的异步编程、异步的单元测试、异步的错误处理以及什么是好的实践。All in all, 限于篇幅,不在此汇总了。最后,async + await 确实是一个很优雅的方案。

The above is the detailed content of Detailed explanation of ES6's async+await synchronization/asynchronous solution. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn