异步函数,async,await那些你忽略的进程

2,298 阅读5分钟

异步函数

  • 异步函数,也称为async/await(语法关键字),是es6promise在JS函数中的应用,这个特性从行为和语法上都增强了JS,让以同步方式写的代码能够异步执行

例子1:这个期约在超时之后会解决为一个值,如果程序中其他代码要访问这个值,则需要一个解决处理函数

let p = new Promise((resolve, reject) => 
setTimeout(resolve, 1000, 3)); 
p.then((x) => console.log(x)); // 3

async

  • async关键字用于声明异步函数,这个关键之可以用在函数声明,函数表达式,箭头函数和方法上,这个关键字可以让函数具有异步的特性,但总体上代码任然是同步求值得,在参数和闭包方面,异步函数任然具有普通JS函数的正常行为
async function foo() {
            console.log(1);
            return 3;
        } // 给返回的期约添加一个解决处理程序
        foo().then(console.log);
        console.log(4);
        console.log(5); // 1 ,4 ,5 , 3

小结:在异步函数如果使用了return返回了值,这个值会被promise.resolve()包装成一个期约函数,异步函数始终返回期约对象,在函数外部调用可以得到他返回的期约

  • 异步函数的返回值期待一个thenable接口的对象,这个对象可以提供给then()的处理程序解包,如果不是,就会被当做已经解决期约结果
  // 返回一个没有实现thenable接口的对象
        async function bar() {
            return ['bar'];
        }
        bar().then(console.log); // ['bar']
// 返回一个实现了thenable接口的非期约对象
        async function baz() {
            const thenable = {
                then(callback) {
                    callback('baz');
                }
            };
            return thenable;
        }
        baz().then(console.log); // baz
  // 返回一个期约
        async function qux() {
            return Promise.resolve('qux');
        }
        qux().then(console.log); // qux

小结:异步函数返回值,含thenable则提供then的处理程序解包,否则把返回值当期约处理结果,但是要注意,拒绝期约的错误不会被异步函数捕获

   async function foo() {
            console.log(1);
            Promise.reject(3);
        }
        // Attach a rejected handler to the returned promise 
        foo().catch(console.log);
        console.log(2);
        // 1
        // 2 
        // Uncaught (in promise): 3

await

  • 因为异步函数主要针对不会马上完成的任务,所以需要一种暂停和恢复执行的能力,使用await可以暂停异步函数的代码执行,等待期约解决
//异步写法
  let p = new Promise((resolve, reject) => 
        setTimeout(resolve, 1000, 3));
        p.then((x) => console.log(x)); // 3
//async写法
   async function foo() {
            let p = new Promise((resolve, reject) => 
            setTimeout(resolve, 1000, 3));
            console.log(await p);
        }

小结:await关键字会暂停执行异步函数后面的代码,让出JS运行时的执行线程,这个行为和生成器中的yield关键字一样,await关键字同样是尝试解包对象的值,然后将这个值传达给表达式,,再异步恢复异步函数的执行

接下来看一个涉及停止与恢复执行的例子:这个例子非常有意思,虽然现在你看不到这种效果,但是能更直白帮你理解await

  • 为了更好的理解这个例子,首先要知道这几个问题

    • await的地方,等于局部暂停,等待返回值,继续其他线程

    • 同样的await按照先后顺序从上往下

    • 当await有多个进程以后,会按照多列顺序执行

      例子1:

async function foo() {
            console.log(await Promise.resolve('foo'));
        }
        async function bar() {
            console.log(await 'bar');
        }
        async function baz() {
            console.log('baz');
        }
        foo();
        bar();
        baz(); 
        // baz // bar // foo

小结:正常情况是这个顺序,但是你在浏览器跑的时候,结果bar和foo的值会调换位子

首先我们说一下执行原理:

  • foo()调用时,foo在消息队列添加两列进程,第一列等待返回值,第二列拿到返回值后执行的进程,
  • 然后继续bar()这时候在消息队列第一列添加一个进程,这时候执行baz,直接打印出‘baz',
  • 然后开始消息队列进程,foo第一列期约得出结果拿到返回值,然后继续bar的消息队列第一列进程,这时候打印出'bar',这时候消息队列第一列进程全部执行完毕,
  • 进入第二列,foo用第一列进程拿到的返回值执行进程,打印foo,

其次我们说一下为什么浏览器的结果不是这样:1TC39 对 await 后面是期约的情况如何处理做过一次修改。修改后,本例中的 foo()里的await Promise.resolve(’foo‘)只会生成一个异步任务。所以他执行的时候没有两列,只有一列,所以会按await顺序排列

  • 按照上面理解,我们来看最终的一个例子
 async function foo() {
            console.log(2);
            console.log(await Promise.resolve(8));
            console.log(9);
        }
        async function bar() {
            console.log(4);
            console.log(await 6);
            console.log(7);
        }
        console.log(1);
        foo();
        console.log(3);
        bar();
        console.log(5);
        // 1 // 2 // 3 // 4 // 5 //8 // 9 // 6// 7

小结:上面显示的最新修改过后的结果,按照原结果进程应该是

  • 打印1,运行foo,打印2,消息队列加入两列,打印3,运行bar,打印4,消息队列加一列进程,打印5,开始执行消息队列第一行,foo拿到返回值,bar打印6,然后打印7,回到foo第二列,打印结果8,再打印9

本文使用 mdnice 排版