限时10分钟,你会怎么实现这段async/await代码?
cnblogs 2024-07-27 08:11:00 阅读 90
🧑💻 写在开头
点赞 + 收藏 === 学会🤣🤣🤣
本文用于记录在React课程中学习时,课程中留下的一个关于<code>async/await原理的思考题(默认读者熟悉
Promise
)
思考题
这个思考题就是:请将以下async/await
代码,换一种方式实现,保证异步等待功能和输出顺序:
function delay(ms, data) {
return new Promise(resolve => setTimeout(resolve, ms, data));
}
const func = async() => {
const data = await delay(2000, 'A');
console.log(data);
const res = await delay(2000, 'B');
console.log(res);
};
func();
这里可以先暂停去实现一下,以下内容从async/await
基本知识开始。
async/await基本介绍
async/await
是一种以更舒服的方式使用promise
的特殊语法,让异步逻辑更加简洁可读,避免promise
的链式写法。
async
首先来介绍async
,该关键字代表函数总是返回promise
,返回的promise
有resolved
的情况和rejected
的情况:
- <li>
- 若函数返回了值,则该值会被
Promise.resolve
包装,被解决的值就是该函数返回的值 - 若函数没有返回值,则
promise
中被解决的值为undefined
resolved
情况如下: // 返回值
const func = async () => {
return 1;
}
// 控制台打印:Promise {<fulfilled>: 1} 'func'
console.log(func(), 'func');
// ------------------------------------------------------
// 没有返回
const func1 = async () => {
}
// 控制台打印:Promise{<fulfilled>: undefined} 'func1'
console.log(func1(), 'func1');
rejected
情况:- 返回错误或是抛出错误,会导致这个
promise
为rejected
- 返回错误或是抛出错误,会导致这个
// ---------------------------返回错误---------------------------
const func = async () => {
return new Error('error');
}
// 控制台打印:Promise{<fulfilled>: Error: error
console.log(func(), 'func')
// ---------------------------抛出错误---------------------------
const func1 = async () => {
throw new Error('error');
}
// 控制台打印:Promise{<fulfilled>: Error: error
console.log(func1(), 'func1')
await
与async
配对使用的就是await
,并且await
只能在async
函数内工作,作用是等待promise
完成并返回结果,这里也分resolved
的情况和rejected
的情况:
resolved
情况:promise
的resolved
情况,被解决的值作为await
表达式的值
const func = async () => {
// await表达式的值就是被解决值'done',然后被赋值给data
const data = await Promise.resolve('done');
}
rejected
情况:promise
的rejected
情况,如果不使用try/catch
捕获,则语句(1)等同于语句(2)的效果,都会抛出错误
const func = async () => {
// 控制台:Uncaught (in promise) error
const data = await Promise.reject('error'); (1)
throw 'error'; (2)
}
关键点
熟悉async/await
之后,就是要准备实现它了;在实现它之前,不妨将目前的特点总结一下:
- 处理的是
Promise
- 能够暂停函数执行
- 能够等待
Promise
解决之后,取出解决值,恢复函数执行
纵观以上的特点,关键点就在于函数的暂停和恢复执行,只要解决它,就能够实现async/await
一样的效果;
查阅资料能发现,在JavaScript
中有一个能够实现函数的暂停与执行的,那就是Generator(生成器)
,所以接下来先了解一下Generator
的基本语法。
Generator简介
Generator
:译为生成器,是ECMAScript 6
新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力;基础代码示例如下:
const func = function* (){
yield 1;
yield 2;
yield 3;
}
const iterator = func();
iterator.next(); // {value: 1, done: false}
iterator.next(); // {value: 2, done: false}
iterator.next(); // {value: 3, done: false}
iterator.next(); // {value: undefined, done: true}
Generator
有以下特点:
- 声明生成器函数需要使用
function* 函数名()
语法,其实function *函数名()
也可以,因为是函数的特殊语法,所以建议使用前者*
靠近function
的写法 - 生成器函数被调用的时候,函数并不会执行,而是返回一个生成器实例
Generator
是Iterator的子类,所以生成器实例具有迭代器的特性- 生成器实例具有
next、return、throw
方法,其主要方法就是next
;当next
被调用时,会恢复函数执行,执行到最近的yield
,然后暂停,并将yield
后的结果返回到外部,也就是next
调用后的value
值 yield
既可以产出值,也可以输入值;给next
方法传入的值,作为上一个yield表达式
的值
接下来看一个使用next
传入值,yield
接收值的例子,也请思考一下打印结果:
const func = function* () {
console.log(1);
const data = yield 2;
console.log(data);
yield 4;
}
const it = func();
console.log(it.next());
console.log(it.next(3));
console.log(it.next());
打印结果如下所示:
可能这里的打印顺序以及逻辑处理,对之前没有接触过生成器知识的朋友有点不知所以,接下来,我来对代码的执行做一个解释(这里用<code>「」代表行数,例如:「8」
表示第8行):
- <li>执行
- 执行
「10」
:- 先调用
next
方法,函数开始执行 - 执行
「2」
,打印1 - 执行
「3」
,遇到yield 2
,暂停执行,返回内容 - 执行
「10」
,打印{value: 2, done: false}
- 先调用
- 执行
「11」
:- 先调用
next
方法,函数从上一次暂停处「3」
恢复执行 - 执行
「3」
,next中的参数作为yield 2
表达式的值;data被赋值为3 - 执行
「4」
,打印3 - 执行
「5」
,遇到yield 4
,暂停执行,返回内容 - 执行
「11」
,打印{value: 4, done: false}
- 先调用
- 执行
「12」
:- 先调用
next
方法,函数从上一次暂停处恢复执行 - 无执行内容,迭代结束
- 执行
「12」
,打印{value: undefined, done: true}
- 先调用
「8」
:执行生成器函数,生成生成器实例,此时函数内部并未执行 实现
思路
经过以上的步骤,对Generator
的暂停和执行的特点有了认识,现在来讲解一下实现思考题的思路。
观察之前的这段代码:
const func = function* () {
console.log(1);
const data = yield 2;
console.log(data);
yield 4;
}
const it = func();
console.log(it.next());
console.log(it.next(3));
console.log(it.next());
可以发现「3」
就比较类似业务代码中的const {data} = await API.xxx()
形式,两者都有等待后表达式的值赋值给左侧的特点;
- 等待后赋值
关键点就在这个“等待后赋值”
上,将上面代码改造为的让data
等待一会儿再被赋值,如下:
const func = function* () {
console.log(1);
const data = yield 2;
console.log(data);
yield 4;
}
const it = func();
console.log(it.next());
// 等待3s再执行
setTimeout(() => {
console.log(it.next(3));
console.log(it.next());
}, 3000);
以上代码让3s之后再执行next(3)
给data
赋值,也就是再被赋值之前,操作空间很大,完全可以等待一些事件完成之后再调用next(3)
将值传入函数内部,且让函数内部继续执行。
如果读者的思路一直跟到这里,那么我相信读者对如何用Generator
和Promise
实现async/await
已经有了一些思路了,不妨先去动手试试,再来看下面的具体代码。
具体代码
那么用Generator
和Promise
实现文章开头的思考题,如下所示:
function delay(ms, data) {
return new Promise(resolve => setTimeout(resolve, ms, data));
}
const func = function* () {
const data = yield delay(2000, 'A');
console.log(data);
const res = yield delay(2000, 'B');
console.log(res);
}
let p1, p2, it = func();
// 接收第一个Promise
p1 = it.next().value;
p1.then((res) => {
// 给data赋值,接收第二个Promise
p2 = it.next(res).value;
p2.then((res) => {
// 执行到最后
it.next(res);
});
});
async/await与Generator/Promise的关系
到这里,思考题的意图就显现出来了;目的就是点出async/await
的原理其实就是Generator+Promise
;再换一句话描述就是:async/await
是Generator+Promise
的语法糖。
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。