对async/await的深入理解

作者: 陈学谦_ | 来源:发表于2017-05-04 19:30 被阅读602次

    五四青年节纪念!!!

    一、前言

    我最初接触 async/await 是在完成百度前端学院的课程时碰到的,当时需要用koa框架,在koa的教程里初次见到 async/await (其实也是第一次接触ES6),以下是文档中的一个小demo中的一段:

    // x-response-time
    
    app.use(async function (ctx, next) {
      const start = new Date();
      await next();
      const ms = new Date() - start;
      ctx.set('X-Response-Time', `${ms}ms`);
    });
    

    起初还不理解这个await是什么意思,实验了一下发现这个 await 会将代码执行的权利交给他后面的函数,等到后面这个函数执行完后再执行之后的代码。

    当时我觉得这个功能可叼了,即使我当时连 JS 是单线程的都不知道,但我还是觉得这个功能可叼了。

    活学活用,用着可爽了,但是后来逐渐发现不对劲了——好像 await 也不是啥都等,当时做百度前端学院作业时,我想执行一个child_process,但是我发现我 await exec(....)他无论如何都不等exec执行完后才继续往下执行。在网上也找了很多资料,我发现居然还有个东西叫Promise,说要在await后面放Promise才行什么的,我擦,真的不知道还有这种东西。最后搞半天也没搞出来,草草收场。(其实主要还是因为自己啥也不懂)

    二、引导小练习

    其实我当时要解决的一个需求很简单,我有一个异步操作,我希望程序等我这个一步操作执行完了再继续向下执行(虽然这个需求很奇异,但是这也让我对async/await有更深入理解有很大的帮助),举个栗子:

    我现在想要如下代码输出 1 2 3

    
    console.log(1);
    
    setTimeout(function () {
      console.log(2);
    }, 1000);
    
    console.log(3);
    
    

    该怎么修改呢?

    当初我的想法就是

    // 注意,这是错误的做法
    
    (async function () {
    
      console.log(1);
      
      await setTimeout(function () {
        console.log(2);
      }, 1000);
      
      console.log(3);
      
    }())
    
    

    你可以在控制台输出看看,你会发现输出的是1 3 2,而且中间还会穿插一个Promise(这个不是你的代码输出的,是浏览器自带魔法)。

    其实正确的做法应该是这样的

    
    (async function () {
    
      console.log(1);
    
      await new Promise(function (resolve, reject) {
        "use strict";
        setTimeout(function () {
          console.log(2);
          resolve();
        }, 1000);
      });
    
      console.log(3);
    
    }())
    
    

    写得更加fashion一些:

    
    (async function () {
    
      console.log(1);
    
      await new Promise(resolve => {
        "use strict";
        setTimeout(() => {
          console.log(2);
          resolve();
        }, 1000);
      });
    
      console.log(3);
    
    }())
    
    

    对,要加一个小小的new Promise(...)

    这是为什么呢?

    三、async/await

    1. async 函数

    我们先看MDN上关于async function怎么说的:

    When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.

    也就是说async函数会返回一个Promise对象。

    • 如果async函数中是return一个值,这个值就是Promise对象中resolve的值;
    • 如果async函数中是throw一个值,这个值就是Promise对象中reject的值。

    举个代码:

    async函数的写法

    async function imAsync(num) {
      if (num > 0) {
        return num // 这里相当于resolve(num)
      } else {
        throw num // 这里相当于reject(num)
      }
    }
    
    imAsync(1).then(function (v) {
      console.log(v); // 1
    });
    
    // 注意这里是catch
    imAsync(0).catch(function (v) {
      console.log(v); // 0
    })
    

    Promise的写法

    function imPromise(num) {
    
      return new Promise(function (resolve, reject) {
        if (num > 0) {
          resolve(num);
        } else {
          reject(num);
        }
      })
    }
    
    imPromise(1).then(function (v) {
      console.log(v); // 1
    })
    
    imPromise(0).then(function (v) {
      console.log(v); // 0
    })
    
    

    2. await

    再来看看MDN对于await是怎么说的:

    An async function can contain an await expression, that pauses the execution of the async function and watis for the passed Promise's resolution, and then resumes the async function's execution and returns the resolved value.

    “await会暂停当前async函数的执行,等待后面的Promise的计算结果返回以后再继续执行当前的async函数。”

    所以我们之前如果单纯的 await setTimeout(...) 或者 await exec(...) 是不行滴,await 不是什么都等,它等待的只是Promise,你如果没有给他返回个Promise,那它还是会继续向下执行。

    所以 await 等待的不是所有的异步操作,等待的只是Promise。

    3. async/await 诞生的初衷(是要解决什么问题?)

    MDN上还说了一句:

    The purpose of async/await functions is to simplify the behavior of using promises synchronously and to perform some behavior on a group of Promises. Just like Promises are similar to structured callbacks, async/await is similar to combining generators and promises.

    “async/await是为了简化多个Promise的同步操作,就像Promise要解决层层嵌套的回调函数的问题一样

    所以async/await就是要完善Promise还不够完美的地方,是在Promise的基础上进行改进的,因此也很好理解为什么await只能“等待”Promise对象。

    四、个人对async/await的理解

    以下是个人理解的async/await,如果有说的不对的地方,你来打我啊!

    async/await是在Promise之后产生的,它和Promise诞生的目的都是为了解决“回调地狱”,至于什么是回调地狱:

    回调地狱

    Promise改进后:

    Promise改进后

    async/await改进后:

    async/await改进后

    图片来自阮一峰的微博

    很牛逼吧!

    所以我发现async/await是在Promise的基础上做了改进,await是接收一个Promise对象,而当Promise执行到resolve()或者reject()的时候(fulfilled和rejected),await才会继续往下执行。

    所以关键点就是得是返回Promise对象的函数才行,不然await等你后面的函数执行完了,见你没返回Promise对象,那他就继续执行了,不管你了。

    所以我们也能看出,Promise和async/await的应用场景是大量连续的异步操作,像我刚刚举的小栗子的这种需求其实也不常见哈。

    所以当时我的 await exec(...)也可以改成:

    
    await new Promise((resolve, reject) => {
      exec(..., function () {
        // 回调函数中
        if (success) {
          resolve();
        } else {
          reject();
        }
      })
    })
    
    

    相关文章

      网友评论

      • 4db00b8fd44f::smirk: 我也是学koa2看到这个的握手握手,看了你的突然明白了一点
        陈学谦_:哈哈,好久以前写的文章了,现在总结一句话就是:
        async和Promise其实是一个东西。
        async函数就是一个Promise,async函数的return相当于Promise的resolve,async函数的throw error相当于Promise的reject。

      本文标题:对async/await的深入理解

      本文链接:https://www.haomeiwen.com/subject/joydtxtx.html