给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
前言
首先我们要知道以下几个相关的知识点:
- async 函数是
AsyncFunction
构造函数的实例, 并且其中允许使用await关键字。 - 当我们使用
async
声明了一个异步函数,那么我们就可以在这个异步函数内部使用await
关键字。 await
关键字只能接在async函数
中使用,不能单独出现。async函数
返回的是一个状态为'fulfilled'
的Promise对象
,如果有 return xxx;那就相当于resolve(xxx);如果没有return,则返回Promise {<fulfilled>: undefined}
的Promise
对象。await
关键字后面最好接的是Promise对象,以便于更好的实现异步(并不意味着不能接非Promise对象)。
官方的async await
function request(num) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(num * 10); }, 1000); }); } async function fn() { const res1 = await request(1) const res2 = await request(res1) console.log(res2); } console.log(fn()); 复制代码
将上一个await的结果
作为第二个await的参数
,最后将第二个await的结果
打印出来。
其实明白Promise.then链式调用
的伙伴就知道,这个效果使用.then
的链式调用就可以实现。当然在.then
里面拿到resolve
出来的结果再给到下一个.then
作为参数,以此往复就可以实现,但是在链式调用传参时,很容易把自己绕晕,而且我如果写了很多个await
那就会嵌套很深,最后的代码会是一大坨,不美观。
那如果我不这样链式使用,我该如何等上一个Promise对象的执行结果出来之后,把结果作为第二个Promise对象的参数呢,这个时候我们就需要知道在ES6中提供了一种为异步编程解决方案的函数Generator函数
,它是我们实现async await
的主角之一,在这我不过多介绍,直接带大家迅速了解他它的用法,我们在实现async await
就是使用了它的简单用法。
Generator函数
Generator
函数是一个状态机,封装了多个内部状态。 执行 Generator
函数会返回一个遍历器对象,也就是说,Generator
函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。形式上,Generator
函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号(*)
;二是,函数体内部使用yield
表达式,定义不同的内部状态。
function* gen() { yield 1; yield 2; yield 3; return 4; } const g = gen() console.log(g); 复制代码
我们直接打印这个函数执行结果看看是什么样子?
诶?怎么就是一个编译好的gen函数呢,那个return 4呢? **Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。**
那如果我在调用next方法的时候传了一个参数,那这个参数会给到谁呢?
诶?为啥好像没啥变化呀?先别急,我换种写法你再看看
原来在next
里面的参数给到了上一次yield
的执行结果,并且在yield
都执行完了之后内部的状态为true
意味着,这个gen函数已经执行完了。
既然await
后面建议接Promise对象
,那我就把yield
后面的换成Promise对象
不就好了吗?这样我不就能控制在上一个Promise对象
执行结束之后拿到了结果,再使用next传参不就是我们最开始想要的效果吗?
给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
只要这个Promise对象
的状态变更为'fulfilled'
,那我就在它的.then里面传参不就好了
generatorToaAsync()
有以上这些基础之后我们来使用一个高阶函数来实现async await
的功能,因为我们不能呢声明像await
一样的关键字
function* gen() { ... } function generatorToaAsync(generatorFn){ // ... return // 具有async函数功能的函数 } const asyncFn = generatorToaAsync(gen); console.log(asyncFn()); // Promise{} 复制代码
我们使用高阶函数
声明一个函数,这个函数接受一个参数(generator函数
),返回出一个具有async功能
的函数
// 模拟async function generatorToaAsync(generatorFn) { // ... //具有async功能的函数 return function () { return new Promise((resolve, reject) => { }) } } 复制代码
这样万里长征的第一步,返回了一个具有async功能的Promise对象。
结合一下我们上面generator的部分例子
function foo(num) { // console.log(num); return new Promise((resolve) => { setTimeout(() => { resolve(num * 2); }, 1000); }) } function* gen() { const num1 = yield foo(1); const num2 = yield foo(num1); const num3 = yield foo(num2); return num3; } // 模拟async function generatorToaAsync(generatorFn) { // ... //具有async功能的函数 return function () { const gen = generatorFn() // 相当于前面加载好整个generator函数,,方面后面.next()去一层一层执行 return new Promise((resolve, reject) => { const next1 = gen.next() next1.value.then(res1 => { const next2 = gen.next(res1) next2.value.then(res2 => { const next3 = gen.next(res2) next3.value.then(res3 => { resolve(gen.next(res3).value) }) }) }) }) } } const asyncFn = generatorToaAsync(gen); // console.log(asyncFn()); // Promise{} asyncFn().then(res => { console.log(res); }) 复制代码
我们借助了generator函数
的next
方法,改变其gen函数
内部yield
的状态,最终我们将gen函数
return 的值再通过我们返回的Promise对象
resolve
出来,让我们看一下效果;
让我们看一眼官方async await实现的效果
是不是和我们实现的效果一致,最后我们只需要优化改进一下,就实现了async函数
的效果;因为上面我们是站在上帝视角,知道gen函数里面有几个yield
这样我们就是调用了三次.then
就可以resolve
出来。
首先我们考虑到this
的指向问题,我们先将作为参数的(generator函数的this绑定到我们的定义的这个高阶函数generatorToaAsync
身上),再将gen函数中执行的Promise函数全部作为参数给到generatorToaAsync
const gen = generatorFn.apply(this, arguments) 复制代码
既然不知道我们要调用几次.then,那么我们就是定义一个函数来判断Promise
的状态,在函数内部使用递归调用
function loop(key,arg) { let res = null; res = gen[key](arg); // == gen.next(arg) // { value: Promise { <pending> }, done: false } const { value, done } = res; if(done) { return resolve(value); }else { // 没执行完yield // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled' Promise.resolve(value).then(val => loop('next',val)); } } loop('next') 复制代码
首先我们执行一个这个函数,将'next'用字符串作为参数,在函数内部使用变量来调用next方法,每一次循环拿到的都是 { value: Promise { <pending> }, done: false }
根据done的状态判断是继续递归,还是resolve
出结果在函数内部声明一个res 存放 每一次的状态和值。这样就实现了最终版的。
function foo(num) { // console.log(num); return new Promise((resolve) => { setTimeout(() => { resolve(num * 2); }, 1000); }) } function* gen() { const num1 = yield foo(1); const num2 = yield foo(num1); const num3 = yield foo(num2); return num3; } // 模拟async function generatorToaAsync(generatorFn) { // ... //具有async功能的函数 return function () { const gen = generatorFn.apply(this, arguments) // const gen = generatorFn() return new Promise((resolve, reject) => { function loop(key,arg) { let res = null; res = gen[key](arg); // 等价于gen.next(arg) // { value: Promise { <pending> }, done: false } const { value, done } = res; if(done) { return resolve(value); }else { // 没执行完yield // Promise.resolve(value) 为了保证value 中 Promise状态已经变更成'fulfilled' Promise.resolve(value).then(val => loop('next',val)); } } loop('next') }) } } const asyncFn = generatorToaAsync(gen); // console.log(asyncFn()); // Promise{} asyncFn().then(res => { console.log(res); 复制代码
写在最后
虽然我们不能使用await
关键字,但是我们可以使用generator函数里面的yield,然后将整个generator函数作为参数传递给我们定义的高阶函数,就能实现官方async await 一样的效果了。
给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库