限时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,返回的promiseresolved的情况和rejected的情况:

    <li>resolved情况如下:
    • 若函数返回了值,则该值会被Promise.resolve包装,被解决的值就是该函数返回的值
    • 若函数没有返回值,则promise中被解决的值为undefined

// 返回值

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情况:
    • 返回错误或是抛出错误,会导致这个promiserejected

// ---------------------------返回错误---------------------------

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情况:
    • promiseresolved情况,被解决的值作为await表达式的值

const func = async () => {

// await表达式的值就是被解决值'done',然后被赋值给data

const data = await Promise.resolve('done');

}

  • rejected情况:
    • promiserejected情况,如果不使用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>执行「8」:执行生成器函数,生成生成器实例,此时函数内部并未执行
  • 执行「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}

实现

思路

经过以上的步骤,对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)将值传入函数内部,且让函数内部继续执行。


如果读者的思路一直跟到这里,那么我相信读者对如何用GeneratorPromise实现async/await已经有了一些思路了,不妨先去动手试试,再来看下面的具体代码。

具体代码

那么用GeneratorPromise实现文章开头的思考题,如下所示:

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/awaitGenerator+Promise语法糖

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。