不看后悔,async和await详解

您所在的位置:网站首页 await造句简单 不看后悔,async和await详解

不看后悔,async和await详解

2024-01-14 08:38| 来源: 网络整理| 查看: 265

async await详解 基础:

async声明该函数是异步的,且该函数会返回一个promise。

await必须放在async函数中使用

await+Promise

这是最常见的场景,await 会等待Promise的状态改为fullfilled,如果成功,那么会将async函数剩余任务推入到微任务队列,如果失败,那么剩余任务不会被推入微任务队列执行,它会返回Promise.reject(err)

await + 普通的值

**即使await右边非函数,只是一个普通的数值,但它本质上是将其转化为 Promise.resolve(2),所以会返回一个成功的promise **

因此,当await等待到了成功的结果后,它会将async函数剩余内容推入到微任务队列中等待执行。

async function run() { console.log('start 1') const res = await 2 console.log(res) console.log('end') } run() console.log('3')

image-20230925153232909

await+函数

一个最简单的场景:

function fn() { console.log('fn start') console.log('fn end') } async function run() { console.log('start 1') const res = await fn() console.log(res) console.log('end') } run() console.log('3')

运行结果为:image-20230925154135052

结论:如果await 右边是一个函数,它会立刻执行这个函数,而且只有当这个函数执行结束后(即函数完成)!才会将async剩余任务推入微任务队列

image-20230925154401107 image-20230925154409457

**这是因为await等待的永远是promise,如果函数返回undefined,那么await会等待函数执行完成,将返回值转化为Promise.resolve(undefined) **(即第二种情况 await + 普通值)。如果函数执行完成后,返回一个失败的promise,那么它将不会再把剩余任务推入到微任务队列。

复杂点的案例,如果fn函数里面嵌套了await

案例: async function async1() { console.log(1) await async2() console.log(2) } const async2 = async () => { await setTimeout((_) => { Promise.resolve().then((_) => { console.log(3) }) console.log(4) }, 0) } const async3 = async () => { Promise.resolve().then(() => { console.log(6) }) } async1() console.log(7) async3()

分析:

首先调用async1 输出1 ,await async2()立刻调用async2(),直至async2函数完成后,才会将cl(2)推入微任务队列

调用async2(), await 定时器,(定时器本身也是个函数),因此先将定时器的回调函数推入宏任务队列,定时器本身返回一个定时器ID

因此,async2可以转化为:

async ()=>{ await 数值; //即第二种情况 }

重点:await 数值会转化为await Promise.resolve(数值),再将async函数中剩余任务推入到微任务队列执行。这时候,async2函数中的剩余任务还有个return undefined,这代表async2函数并不能立刻执行完毕,会将return undefined推入到微任务队列中(这才代表着async2函数真正执行结束)

目前:宏任务队列:定时器回调函数任务

​ 微任务队列:return undefined(async2函数执行完毕)

回到开始,await async2(),目前async2还没有执行结束,因此调用cl(7) 调用async3(),微任务队列推入 cl(6)

目前:宏任务队列:定时器回调函数任务

​ 微任务队列:return undefined(async2函数执行完毕) cl(6)

从微任务队列中取出第一个任务,return undefined,async2()函数执行完毕,await async2() 转化为 Promise.resolve(undefined),因此将cl(2) 推入微任务队列

所以真正的结果是:1 7 6 2 4 3

image-20230925161025826

案例2:

async function async1() { console.log(1) await async2() console.log(2) } const async2 = async () => { await (async () => { await (() => { console.log(3) })() console.log(4) })() } const async3 = async () => { Promise.resolve().then(() => { console.log(6) }) } async1() console.log(7) async3()

思路:

首先跟开始一样,调用async1() 输出1,遇到await async2(),进入等待状态,等待async2()函数完成

调用async2函数,遇到await 立即执行函数1,立即执行立即执行函数1,又遇到了await 立即执行函数2,继续执行立即执行函数2,输出3。

将立即执行函数1的剩余任务,cl(4)和立即执行函数1完成(return undefined)推入微任务队列

注意:必须等待到立即执行函数1完成后,才会将async1剩余任务完成推入微任务队列

目前:宏任务队列:

​ 微任务队列:cl(4) return undefined(即立即执行函数1完成)

因为async2函数内部 await 立即执行函数1,所以它需要等待立即执行函数1完成后,将async2函数执行完成推入微任务队列,再将cl(2)推入微任务队列

输出7

调用async3 ,将cl(6)推入微任务队列

目前:宏任务队列:

​ 微任务队列:cl(4) return undefined(即立即执行函数1完成) cl(6)

同步任务完成,调用微任务队列任务,输出4,立即执行函数1完成,将async2完成推入微任务队列,将cl(2)推入微任务队列。

目前:宏任务队列:

​ 微任务队列: cl(6) async2完成 cl(2)

所以最后的结果是:1 3 7 4 6 2。

image-20230925162429385

自实现一个洋葱圈模型

我们前面已经学习了async和await真正的执行顺序,接下来我们可以以此实现一个洋葱圈模型。(如果不了解何为洋葱圈模型,可以去看一下本人的nodejs篇)

class TaskPro { constructor() { this._taskList = [] this._isRunning = false this._currentIndex = 0 //调用下一个中间件 this._next = async () => { this._currentIndex++ await this._runTask() } } addTask(task) { this._taskList.push(task) } run() { if (this._isRunning || !this._taskList.length) { return } this._isRunning = true //将任务取出来运行 this._runTask() } async _runTask() { if (this._currentIndex >= this._taskList.length) { //任务完成 this._reset() return } const task = this._taskList[this._currentIndex] let i = this._currentIndex await task(this._next) let j = this._currentIndex //如果没有调用next,就任务完成后自行调用 if (i == j) { this._next() } } _reset() { this._currentIndex = 0 this._isRunning = false this._taskList = [] } } //调用 const t = new TaskPro() t.addTask(async (next) => { console.log('1 start') await next() console.log('1 end') }) t.addTask(() => { console.log('2') }) t.addTask(() => { console.log('3') }) t.run()

image.png

原理:学习sunshine_Lin大佬,加入个人理解

以上的几个案例搞懂了,足够解决面试中的async和await的笔试题,接下来是原理部分。

async、await本质上是 一种语法糖,它是基于 generator生成器函数实现的

generator

生成器函数有个特点,就是函数名前会多个 *,只有在generator函数中,才可以调用 yield,且生成器函数会返回一个可迭代的迭代器

function* gen() { yield 1 yield 2 yield 3 } yield + 普通数值

yield:可以把其理解为暂停点,当生成器函数执行时,遇到 yield就会中断执行,只有迭代器调用 next()方法,才会继续执行.

next方法执行后会返回一个对象,对象中有value 和 done两个属性。

function* gen() { yield 1 yield 2 yield 3 } const iterator = gen() console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next()) console.log(iterator.next())

image-20230926172003704

可以看到最后一个是undefined,这取决于你generator函数有无返回值

yield + 函数

yield后面接函数,如果到了对应暂停点,会立刻执行该函数,且该函数的返回值,会作为此yield返回对象的value值

image-20230926172416457image-20230926172428109

yield + promise

前面说了,函数执行返回值会当做暂停点对象的value值,那么下面例子就可以理解了,前两个的value都是pending状态的Promise对象(因为是异步的关系,但是点开看,promise变为成功)

function fn(num) { return new Promise(resolve => { setTimeout(() => { resolve(num) }, 1000) }) } function* gen() { yield fn(1) yield fn(2) return 3 } const g = gen() console.log(g.next()) // { value: Promise { }, done: false } console.log(g.next()) // { value: Promise { }, done: false } console.log(g.next()) // { value: 3, done: true }

但是,我们想要的promise结果是 1和 2,其实也很简单,它返回的对象.value.then()即可以获取到成功或失败的结果

function fn(num) { return new Promise((resolve) => { setTimeout(() => { resolve(num) }, 1000) }) } function* gen() { yield fn(1) yield fn(2) return 3 } const g = gen() let value1 = g.next() value1.value.then((v) => { console.log(v) }) next函数传参

generator返回的迭代器,每次调用next()方法,还可以传递参数,参数会作为yield的返回值

注意:

第一次next传参是无效的,只有第二次next传参才有效 next传参时,先执行yield右边,它会修改返回的对象的value值,然后把next的参数作为yield的返回值 function* gen() { const num1 = yield 1 console.log(num1) const num2 = yield 2 console.log(num2) return 3 } const g = gen() console.log(g.next()) // { value: 1, done: false } //第一次传参是无效的 console.log(g.next(11111)) console.log(g.next(22222)) console.log(g.next())

image-20230926173352600

组合起来的效果

function fn(nums) { return new Promise((resolve) => { setTimeout(() => { resolve(nums * 2) }, 1000) }) } function* gen() { const num1 = yield fn(1) const num2 = yield fn(num1) const num3 = yield fn(num2) return 'xxx' } const iterator = gen() let next1 = iterator.next() //返回一个1s后改变状态的promise next1.value.then((v) => { console.log(v) //1s后输出2 let next2 = iterator.next(v) //next传参,num1的值即为v,调用fn函数,返回一个1s后改变状态的promise next2.value.then((v2) => { console.log(v2) //再过1s后输出4 let next3 = iterator.next(v2) //num2的值为v2 4 next3.value.then((v3) => { console.log(v3) let finalNext = iterator.next() console.log(finalNext) }) }) })

这就跟async和await很像了,但是区别在于

gen函数返回值不是promise,async函数返回值是promise gen函数需要进行一系列的next操作,async函数不需要

我们可以封装一个高阶函数。什么是高阶函数呢?高阶函数的特点是:参数是函数,返回值也可以是函数。下方的highorderFn就是一个高阶函数

function highorderFn(函数) { // 一系列处理 return 函数 } function fn(nums) { return new Promise((resolve) => { setTimeout(() => { resolve(nums * 2) }, 1000) }) } function* gen() { const num1 = yield fn(1) const num2 = yield fn(num1) const num3 = yield fn(num2) return num3 } function generatorToAsync(generatorFn) { //返回一个具有async函数功能的函数 return function () { return new Promise((resolve, reject) => { const iterator = generatorFn() const next1 = iterator.next() next1.value.then((v) => { console.log(v) //1s后输出2 let next2 = iterator.next(v) //next传参,num1的值即为v,调用fn函数,返回一个1s后改变状态的promise next2.value.then((v2) => { console.log(v2) //再过1s后输出4 let next3 = iterator.next(v2) //num2的值为v2 4 next3.value.then((v3) => { console.log(v3) //再过1s后输出8 let finalNext = iterator.next(v3) //将v3传递给num3 resolve(finalNext.value) }) }) }) }) } } const asyncFn = generatorToAsync(gen) asyncFn().then((v) => { console.log(v) //3s后输出8 })

至此,其实就实现了async和await的效果了

最终版本 function generatorToAsync(generatorFn) { return function () { //生成迭代器 const gen = generatorFn.apply(this, arguments) // gen有可能传参 // 返回一个Promise return new Promise((resolve, reject) => { function go(key, arg) { let res try { res = gen[key](arg) // 这里有可能会执行返回reject状态的Promise //即调用gen.next(arg),并传入参数 } catch (error) { return reject(error) // 报错的话会走catch,直接reject } // 解构获得value和done const { value, done } = res if (done) { // 如果done为true,说明走完了,进行resolve(value) return resolve(value) } else { // 如果done为false,说明没走完,还得继续走 // value有可能是:常量,Promise,Promise有可能是成功或者失败 //注意,如果传入的promise,那么它会简单的直接返回该promise对象 return Promise.resolve(value).then( (val) => go('next', val), //如果还没结束,就再次调用go方法,同时传递结果值 (err) => go('throw', err), ) } } go('next') // 第一次执行 }) } } function* gen() { const num1 = yield fn(1) console.log(num1) // 2 const num2 = yield fn(num1) console.log(num2) // 4 const num3 = yield fn(num2) console.log(num3) // 8 return num3 } const genToAsync = generatorToAsync(gen) const asyncRes = genToAsync() console.log(asyncRes) // Promise asyncRes.then((res) => console.log(res)) // 8


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3