async 实现原理分析


async 实现原理分析

一、简要概述

async 函数,是一种对异步函数更加优雅的处理方式,本质是 Generator 函数的语法糖。所以为了更好的阅读本博客,建议对以下知识点有所了解:

  1. 遍历器对象(Generator 函数的返回值是一个遍历器对象)
  2. Generator 函数基础语法(函数定义、yield 表达式、yield * 表达式)
  3. Generator.prototype.return 和 Generator.prototype.throw

async 函数与 Generator 函数不同在于下面几点:

  1. async 内置执行器,不用像 Generator 函数那样去反复调用返回遍历器的 next 方法
  2. 语义更好:async 表示异步,await 表示等待异步结果
  3. await 后既可以是 Promise 对象,也可以是原始类型的值
  4. async 函数的返回值不是遍历器对象,而是一个 Promise 对象,

二、基础语法

正常使用:

let a = async () => {
	let a = await Promise.resolve(1); // await 将后面 Promise 实例的值返回
	let b = await 2; 
	return a + b; 
}
a().then(res=>console.log(res))
// a() 返回一个 promise 实例,
// a 函数里 return 的值会作为成功回调参数

错误处理:

let a = async () => {
	await Promise.reject(1);
    console.log('这里不会执行');
	// await 后面的 Promise 变为 reject,整个 async 函数的 Promise 实例也会 reject,并且会中断函数后面的执行
}
a().catch(err=>console.log(err));

三、实现原理

async 函数的实现原理就是将 Generator 函数和自动执行器包装在一个函数里。

async function fn(args)=> {
	...
}
// 等同于
function fn(args) {
	return spawn(function * () {
	...
	})
}

spawn 函数的实现如下:

function spawn(genF) {
	return new Promise((resolve, reject)=>{
        // genF 是 Generator 函数,gen 是生成的遍历器对象
		let gen = genF(); 
        // 定义分步函数
		let step = (nextF) => {
            /* async 函数的每一步,都是用 try catch 执行的,一旦有错,立刻 reject */
			try {
				let next = nextF();
			} catch(err) {
				return reject(e);
			}
            /* async 执行完最后一步,return 的值会作为成功回调参数 */
			if (next.done) {
				return resolve(next.value);
			}
            /* asyc 还没有执行完,await 后值存放在 next.value 
               将 next.value 转化为 promise 实例,然后指定回调为 step 
               就可以实现 Generator 函数的自动执行 */
			Promise.resolve(next.value).then(
				v => {step( ()=>gen.next(v) )}, // v 作为 await 返回值 
				e => {step( ()=>gen.throw(e) )} // reject 时 await 没有返回值
			)
		}
		// 开始执行 async 函数
        step( ()=>gen.next() ); 
        
	})
}

async 函数 return 原理:

async 函数本质还是 Generator 函数,是通过不断执行遍历器对象的 next 方法来执行函数。当遍历器对象遍历完毕,就将最后遍历 (return) 的值 resolve 出来传递给成功回调

async 函数自动执行原理:

await 有两个作用:

  1. 和 yield 一样,可以将函数的执行进行切割和分步
  2. 可以将 await 后面的值转化为 promise 实例,然后指定它的回调。所以通过将 async 函数的下一步执行指定为这个 promise 实例的回调,就可以实现 async 的自动执行。

await 返回值原理

我们知道, Generator 函数遍历器的 next 方法的参数,会作为上一个 yield 的返回值。而 await 会指定其后 promise 的成功回调执行遍历器对象的 next 方法,我们再把 promise 的成功返回值作为 next 方法的参数,这个值就会成为 await 的返回值

async 出错处理机制

分两类:

  1. async 函数里的语句执行出错。因为 async 函数的每一步,都是通过 try catch 执行的,所以错误都会被捕获到然后 reject 传递给错误回调
  2. await 后的 promsie 实例被 reject 了。这样会触发 await 给该 promise 指定的错误回调,这个错误回调会调用 Generator.prototype.throw 扔一个错误到 async 函数里面,如果里面有 try catch 接住就让它接住,然后继续执行函数 (此时 await 语句出错,不会有返回值);如果里面没有 try,这个 throw 的错误会被第一点所说的 try 接住,然后 reject 传递到错误回调。
/* 1. 错误被外部接住 */ 
(async () => {
	await Promise.reject(1); // 这个 await 是无返回值的
	// 等同在这里执行了: throw 1
})()
.catch(err => console.log(err));

/* 2. 错误被内部接住 */ 
(async () => {
	try {
		await Promise.reject(1); // 这个 await 是无返回值的
		// 等同在这里执行了: throw 1
	} catch(e) {
		console.log(e);
	}
})()
.catch(err => console.log(err));

五、拓展

let b = async ()=> {
	Promise.reject(22);
}
b().then(null, err=>console.log(err));

上面这个例子,会直接报错,不会捕捉到错误然后执行错误回调。

这是为什么呢?

首先我们要知道 Promise 触发错误回调的前提:执行 reject 函数

而执行 reject 函数,有两个前提:

  1. 开发者调用了 reject 函数
  2. try catch 执行函数的时候捕获到错误,将错误 reject 出来。

其中,第一个是开发者可见,第二个是源码底层实现,开发者不可见

比如这个例子

new Promise(()=>{
	throw 1;
}).catch(err=>console.log(err))

// 异步函数其实会被 try catch 执行,一旦捕获到错误,就 reject 掉
try {
	throw 1
} catch (err) {
	reject(err)
}

// 而对于下面这个例子,错误是根本捕捉不到的
try {
	Promise.reject(22);
} catch (err) {
	reject(err)
}

所以我们知道,promise 真正捕获到错误触发错误回调,关键还是在于 reject 函数是否有执行,而不是函数内部是否真的发生了错误。

回到开始,对于这个例子:

let b = async ()=> {
	Promise.reject(22);
}
b().then(null, err=>console.log(err));

而在刚刚介绍 async 函数实现原理的时候,我们知道 async 函数在执行每一步的时候,也是用 try catch 去执行的:

try {
	Promise.reject(22);
} catch (err) {
	reject(err)
}

刚刚我们已经说明过了,这样子是捕捉不到错误的。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM