about-async-generator

一直都在听说async函数是一种语法糖,可是它到底是谁的语法糖呢?其实是生成器/迭代器的语法糖!可这里面不止这些,async函数还与promise的知识息息相关,下面来一个个点来揭示原理。

首先,我们先声明一个async函数来看看长什么样:

1
2
3
4
5
6
const asyncFn = () => {
	const result = await new Promise(resolve => {
		setTimeout(() => result(1), 2000);
	});
	const sum = result + 1; // 2
}

不知道大家有没有这样的疑问,到底为什么这个async函数它这么智能地等待await后面的promise成功以后,并且竟然还把promise成功的结果(也就是调用resolve(1)里的数字1)赋值给了result,而且await后面不光可以是一个promise,如果是await 1234这种基本类型值,它也会把这个值赋值给result,不觉得有些邪门吗?

一定有幕后黑手🤚,下意识这么猜想了一下,那么有吗?

有的,那就是spawn函数,它利用了生成器与迭代器的特性,什么特性?迭代器竟然可以不停地调用自己的next方法!而且这个next方法还给我们返回了一个结果对象,这个结果对象它有两个属性👉 value、done。spawn函数接收一个生成器函数,然后不停地调用这个生成器生成的迭代器,直到结果对象里的done变为true才停下来。

那又怎样,async函数它也不是生成器啊!可能有人会这样反驳,而其实,底层真的把async函数转成了生成器,await关键字全部变成了yield关键字(生成器特有的关键字),然后传进了spawn函数!:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function *asyncToGenerator() {
	const result = yield new Promise(resolve => {
		setTimeout(() => result(1), 2000);
	});
	const sum = result + 1; // 2
}

function fakeAsync(args) {
	spawn(asyncToGenerator, this, ...args)
}
fakeAsync();

OK,到现在为止,最大的问题就在于spawn函数的实现了,我们来看下是如何实现的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function spawn(generator, context, args) {
    return new Promise((resolve, reject) => {
        const iterator = generator.call(context, ...args);

        const step = getNextValue => {
            let result;
            try {
                result = getNextValue();
            } catch (error) {
                reject(error);
                return;
            }

            if (result.done) {
                resolve(result.value);
                return;
            }

            Promise.resolve(result.value).then(
				(result) => step(() => iterator.next(result)), 
				(reason) => step(() => iterator.throw(reason));
          	)
        }
    
        step(() => iterator.next(undefined))
    })
    
}

spawn函数返回了一个promise,而我们的async函数也是返回一个promise,所以此处效果一样。而后面的逻辑也正如我们所想, step函数被不停地调用,每次调用都是在调用迭代器的next方法,直到result.done为true,才停下。

以上就是关于async函数的所有了👆。

参考文档:

ecmascript-asyncawait/#desugaring

updatedupdated2020-07-062020-07-06