如何理解async/await

1,018 阅读3分钟

今天一朋友去大厂面试完之后回来就跟我疯狂吐槽,说问得好深,感觉回答得不太好。

其中有一题就是问:你是如何理解async函数的。

大致面试过程如下:
朋友:async函数可以结合promise用于控制异步流程,以同步的思考方式来编写异步的代码,可以提高代码的可读性。
面试官:你这只是说明了使用的方法还有它的好处,还有其他的吗?它的原理是什么?
朋友:emmm.........

当时心里就在想,大厂不亏是大厂,连语法糖都不放过,不过想想也是,一般大厂如果问到这种简单的问题,回答一般都是要牵涉到原理的,那async/await的原理到底是什么?

原理

async函数的原理其实就是generator函数结合一个自动迭代器。

这里建议先去了解一下ES6中的generator

我们先来看一个简单的例子:

let it = null;

function* gen() {
    let data = yield getData();    
    console.log(data);
}

function getData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Hello Generator');
        }, 1000);
    })
        .then(
            (data) => {
                // 异步数据获取成功后,通过调用迭代器的next方法将data传入data
                it.next(data);
            },
            ...
        );
}

// 创建迭代器
it = gen();
// 启动generator迭代器,执行到第一个yiled语句
it.next();

这里我们启动it.next(),generator执行到第一个yiled语句后暂停,这时候getData执行,数据异步获取后在内部通过调用it.next(data)将数据data传输到generator内部,这时generator继续执行内部的data就被赋值了'Hello Generator'并打印出来。

// 打印:Hello Generator

上面我们是通过了Promise内部去控制了Generator的执行,从而让我们能以一种同步的方式去控制异步的流程。这只是一个雏形,我们可不可以以async函数使用方式为蓝本用Generator和Promise去创建这样一种模式呢?答案是可以的,我们只需要加一个会自动迭代Generator的启动器就可以了。

首先我们用Generator模拟一下async函数,先创建两个异步操作p1和p2,用于在1s后异步获取值

let p1 = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 1000)
    });
}

let p2 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2);
    }, 1000)
  });
}

接着我们写一个Generator函数:

function* gen() {
    let x = yield p1();
    console.log(`x: ${x}`);
    let y = yield p2();
    console.log(`y: ${y}`);
}

其实到这里已经非常像async函数了,下面是async版本

async gen() {
    let x = await p1();
    console.log(`x: ${x}`);
    let y = await p2();
    console.log(`y: ${y}`);
}

现在我们就只差一个会自动迭代Generator的启动器就可以完美的模拟async函数了。下面我们来编写一个简单的run函数

function run(gen) {
    let it = gen();
    
    return Promise.resolve(it.next())
        .then(
            function handleNext(next) {
                // 结束执行
                if (next.done) return;
                
                // 传入Promise
                return Promise.resolve(next.value)
                    .then(
                        (res) => {
                            // 将Promise的结果输入回生成器当中,并继续执行
                            handleNext(it.next(res));
                        }
                    )
                }
        )
}

// 将gen生成器函数传入,gen函数就会执行
run(gen);

// 打印:
// x: 1 
// y: 2

run函数执行会先执行传入的生成器得到一个迭代器,返回的yield对象如果还能继续执行则将会等到当前Promise完成后自动进行下一个迭代。

原理大致就是这样了,继续撸码了。