一线大厂高级前端编写,前端初中阶面试题,帮助初学者应聘,需要联系微信:javadudu

前端异步编程规范

前端异步编程规范

一.Promise介绍

1.什么是Promise

Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大
在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码:

doSomething(function (result) {
    doSomethingElse(result, function (newResult) {
        doThirdThing(newResult, function (finalResult) {
            console.log('得到最终结果: ' + finalResult);
        }, failureCallback);
    }, failureCallback);
}, failureCallback);

阅读上面代码,是不是很难受,上述形成了经典的回调地狱。

现在通过Promise的改写上面的代码:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);

2.Promise 的用法

Promise对象是一个构造函数,用来生成Promise实例:

let p = new Promise((resolve, reject) => {});

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject

  • resolve函数的作用是,将Promise对象的状态从 “未完成” 变为 “成功”;
  • reject函数的作用是,将Promise对象的状态从 “未完成” 变为 “失败”;

3.Promise 的实例方法

Promise构建出来的实例存在以下方法:

  • then();
  • catch();
  • finally();

3.1 then()

then是实例状态发生改变时的回调函数,第一个参数是回调函数resolve,第二个参数是回调函数reject

then方法返回的是一个新的Promise实例,这个也就是Promise能链式书写的原因。

3.2 catch()

catch()方法是.then(null, reject).then(undefined, reject)的别名,用于指定发生错误时的回调函数。

在我们平时兼容接口返回错误的情况会经常使用:

getJSON('/posts.json').then(posts => {
  // ...
}).catch(error => {
  // 处理 getJSON 和 前一个回调函数运行时发生的错误
  console.log('发生错误!', error);
});

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止:

getJSON('/post/1.json').then(post => {
  return getJSON(post.commentURL);
}).then(comments => {
  // console.log(comments);
}).catch(error => {
  // 处理前面三个Promise产生的错误
});

3.2 finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

new Promise((resolve, reject) => {...})
.then(res => {···})
.catch(error => {···})
.finally(() => {···});

4.Promise 构造函数方法

Promise构造函数存在以下方法:

  • all()
  • race()
  • allSettled()
  • any()

这四个方法都是用来处理 Promise 实例数组的,我们后面手动封装时会详细说到,所以这里先略过。

想必看到这里,你一定很好奇这样一个神奇的函数是如何实现的,那就让我们来手动封装一个 自己的 Promise吧!

二.简版Promise

1.resolve 和 reject

我们用 Promise 生成三个实例:
p1先执行resolve,然后再执行reject;
p2先执行reject,然后再执行resolve;
p3直接throw异常:

let p1 = new Promise((resolve, reject) => {
    resolve('success')
    reject('fail')
})
console.log('p1', p1)

let p2 = new Promise((resolve, reject) => {
    reject('success')
    resolve('fail')
})
console.log('p2', p2)

let p3 = new Promise((resolve, reject) => {
    throw('error')
})
console.log('p3', p3)

我们来看下控制台的打印结果:

前端异步编程规范

这里说明了Promise的四个特点:

  1. 执行了 resolve,Promise 状态会变成 fulfilled;
  2. 执行了 reject,Promise 状态会变成 rejected;
  3. Promise 状态不可逆,第一次成功就永久为 fulfilled,第一次失败就永远状态为 rejected;
  4. Promise 中有 throw 的话,就相当于执行了 reject。

1.1 实现 resolve 和 reject

  1. Promise 需要接受两个回调函数:resolvereject
  2. Promise 需要一个状态值PromiseState和一个返回结果PromiseResultPromiseState 初始状态是pendingPromiseResult初始值是 null;
  3. Promise 需要对 resolve 和 reject 绑定this:确保 resolve 和 reject 的 this 指向永远指向当前的MyPromise实例,防止随着函数执行环境的改变而改变;
  4. 根据执行 resolve() / reject(),改变 PromiseState 和 PromiseResult 的值,即完成了我们MyPromise的一个初步效果。

代码如下:

class MyPromise {
    // 构造方法
    constructor(executor) {
    
        // 初始化值
        this.initValue()
        // 初始化this指向 
        this.initBind()
        // 执行传进来的函数
        executor(this.resolve, this.reject)
    }

    // 初始化值
    initValue() {
        this.PromiseResult = null // 终值
        this.PromiseState = 'pending' // 状态
    }
    
    // 初始化this
    initBind() {
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }

    resolve(value) {
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
    }

    reject(reason) {
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
    }
}

测试如下:

let p1 = new MyPromise((resolve, reject) => {
    resolve('success')
})
console.log(p1) 
// MyPromise { PromiseState: 'fulfilled', PromiseResult: 'success' }

let p2 = new MyPromise((resolve, reject) => {
    reject('fail')
})
console.log(p2) 
// MyPromise { PromiseState: 'rejected', PromiseResult: 'fail' }

注意:

  1. 上述 MyPromise 使用 es6 构造函数的 class 语法声明;

我们对比下 es5 和 es6 两种构造函数的声明方式:

// es5:
function Person(name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        console.log("I am ", this.name);
    }
}
// es6:
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    sayName() {
        console.log("I am ", this.name);
    }
}
  1. excutor是执行器,执行器就是一个函数;
  2. 对于 initBind 绑定 this 指向需要详细说明一下;

有一些同学可能会有疑问,构造函数的 this 不是本来就指向它的实例吗?为什么还要手动再绑定一次?
对于这个问题我们先来打印下 initBind()resolve() 里面的 this:

前端异步编程规范

前端异步编程规范
我们发现,如果不绑定this,执行到 resolve 方法时,this 指向了 undefined。

我们来看一下 resolve() 的调用位置:

前端异步编程规范

执行到 resolve('success') 时,即在执行 executor 的时候,p1 中还没有将 MyPromise 中拥有的成员方法继承过来,即 p1 上还没有这个方法,但是构造函数上有这个方法,所以我们需要提前手动绑定this,让实例的方法绑定到构造函数上。

1.2 状态不可变

let p1 = new MyPromise((resolve, reject) => {
    resolve('success')
    reject('fail')
})
console.log(p1) 
// MyPromise { PromiseState: 'rejected', PromiseResult: 'fail' }

正确的应该是状态为fulfilled,但这里状态又变成了rejected。

Promise有三种状态:

  • pending:等待中,是初始状态;
  • fulfilled:成功状态;
  • rejected:失败状态;

一旦状态从 pending 变为 fulfilled 或者 rejected,那么此Promise实例的状态就不可以改变了。

前端异步编程规范
这步只需要:

resolve(value) {
    // state是不可变的
    if (this.PromiseState !== 'pending') return
        
    this.PromiseState = 'fulfilled'
    this.PromiseResult = value
}

reject(reason) {
    // state是不可变的
    if (this.PromiseState !== 'pending') return
       
    this.PromiseState = 'rejected'
    this.PromiseResult = reason
}

也就是:

class MyPromise {
    // 构造方法
    constructor(executor) {
        // 初始化值
        this.initValue()
        // 初始化this指向
        this.initBind()
        // 执行传进来的函数
        executor(this.resolve, this.reject)
    }
    
    // 初始化值
    initValue() {
        
        this.PromiseResult = null // 终值
        this.PromiseState = 'pending' // 状态
    }
    
    // 初始化this指向
    initBind() {
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }

    resolve(value) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
    }

    reject(reason) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
    }
}

结果如下:

let p2 = new MyPromise((resolve, reject) => {
    // 只以第一次为准
    resolve('success')
    reject('fail')
})
console.log(p2) // 
MyPromise { PromiseState: 'fulfilled', PromiseResult: 'success' }

1.3 throw

Promise 中有 throw 的话,就相当于执行了reject:

前端异步编程规范

这个地方在代码中就要使用try catch了。

1.3.1 try catch

不管你多么精通编程,有时我们的脚本总还是会出现错误。可能是因为我们的编写出错,或是与预期不同的用户输入,或是错误的服务端响应以及其他数千种原因。

通常,如果发生错误,脚本就会“死亡”(立即停止),并在控制台将错误打印出来。

但是有一种语法结构 try...catch,它使我们可以“捕获(catch)”错误,因此脚本可以执行更合理的操作,而不是死掉。

语法:
try...catch 结构由两部分组成:try 和 catch

try {
  //...
} catch (err) {
// 错误捕获
}

它按照以下步骤执行:

  1. 首先,执行 try {...} 中的代码;
  2. 如果这里没有错误,则忽略 catch (err):执行到 try 的末尾并跳过 catch 继续执行;
  3. 如果这里出现错误,则 try 执行停止,控制流转向 catch (err) 的开头,变量 err(我们可以使用任何名称)将包含一个 error 对象,该对象包含了所发生事件的详细信息。

前端异步编程规范

所以,try {...} 块内的 error 不会杀死脚本 —— 我们有机会在 catch 中处理它。

1.3.2 throw

我们将try…catch加到我们的核心代码中:

try {
    // 执行传进来的函数
    executor(this.resolve, this.reject)
} catch (e) {
    // 捕捉到错误直接执行reject
    this.reject(e)
}

完整代码为:

class MyPromise {
    // 构造方法
    constructor(executor) {
        // 初始化值
        this.initValue()
        // 初始化this指向
        this.initBind()
        
        try {
            // 执行传进来的函数
            executor(this.resolve, this.reject)
        } catch (e) {
            // 捕捉到错误直接执行reject
            this.reject(e)
        }
    }
    
    // 初始化值
    initValue() {
        
        this.PromiseResult = null // 终值
        this.PromiseState = 'pending' // 状态
    }
    
    // 初始化this指向
    initBind() {
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }

    resolve(value) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
    }

    reject(reason) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
    }
}

测试代码:

let p3 = new MyPromise((resolve, reject) => {
    throw('fail')
})
console.log(p3) 
// MyPromise { PromiseState: 'rejected', PromiseResult: 'fail' }

2.then

平时业务中 then 的使用一般如下:

// 马上输出 'success'
let p1 = new Promise((resolve, reject) => {
    resolve('success')
}).then(res => console.log(res), err => console.log(err))

// 1秒后输出 'fail'
lett p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('fail')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

// 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => 2 * res, err => console.log(err))
  .then(res => console.log(res), err => console.log(err))

根据上述代码可以确定:

  1. then 接收两个回调,一个是成功回调,一个是失败回调;
  2. 当 Promise 状态为 fulfilled 时,执行成功回调;为 rejected 时,执行失败回调;
  3. 如 resolve 或 reject 在定时器里,则定时器结束后再执行 then;
  4. then 支持链式调用,下一次 then 执行受上一次 then 返回值的影响。

2.1 实现then

// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {

    // 参数校验,确保一定是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    if (this.PromiseState === 'fulfilled') {
        // 如果当前为成功状态,执行第一个回调
        onFulfilled(this.PromiseResult)
    } else if (this.PromiseState === 'rejected') {
        // 如果当前为失败状态,执行第二个回调
        onRejected(this.PromiseResult)
    }

}

完整代码为:

class MyPromise {
    // 构造方法
    constructor(executor) {
        // 初始化值
        this.initValue()
        // 初始化this指向
        this.initBind()
        
        try {
            // 执行传进来的函数
            executor(this.resolve, this.reject)
        } catch (e) {
            // 捕捉到错误直接执行reject
            this.reject(e)
        }
    }
    
    // 初始化值
    initValue() {
        
        this.PromiseResult = null // 终值
        this.PromiseState = 'pending' // 状态
    }
    
    // 初始化this指向
    initBind() {
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)
    }

    resolve(value) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
    }

    reject(reason) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
    }
    
    // 接收两个回调 onFulfilled, onRejected
    then(onFulfilled, onRejected) {

        // 参数校验,确保一定是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

        if (this.PromiseState === 'fulfilled') {
            // 如果当前为成功状态,执行第一个回调
            onFulfilled(this.PromiseResult)
        } else if (this.PromiseState === 'rejected') {
            // 如果当前为失败状态,执行第二个回调
            onRejected(this.PromiseResult)
        }

    }
}

测试代码:

// 输出 'success'
let p = new MyPromise((resolve, reject) => {
    resolve('success')
}).then(res => console.log(res), err => console.log(err))

2.2 定时器

// 1秒后输出 'fail'
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('fail')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

我们用刚刚封装的 MyPromise 去模拟一下上面的定时器的案例:

// 控制台没有打印
let p2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        reject('fail')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

我们发现控制台没有打印任何结果。

这是因为代码执行到then的时候, resolve 和 reject还没有执行,PromiseStatus 仍然为 pending,而then里面没有pending对应的执行逻辑。

前端异步编程规范

那如何保证代码能够在1s后,执行then的回调?

我们不能确保1s后才执行then函数,但是我们可以保证1s后再执行then里的回调。

前端异步编程规范

在这1s时间内,我们可以先把then里面的两个回调保存起来,然后等到1s过后,执行了 resolve 或者 reject,再去判断状态,根据状态判断要去执行刚刚保存的两个回调中的哪一个回调。

那么问题来了,我们怎么知道当前1s还没走完甚至还没开始走呢?

其实很好判断,只要状态是 pending,那就证明定时器还没跑完,因为如果定时器跑完的话,那状态肯定就不是pending,而是fulfilled或者rejected

那用什么来保存这些回调呢?

建议使用数组,因为一个promise实例可能会多次then,用数组就一个一个保存起来就可以了。

    initValue() {
        // 初始化值
        this.PromiseResult = null // 终值
        this.PromiseState = 'pending' // 状态
+       this.onFulfilledCallbacks = [] // 保存成功回调
+       this.onRejectedCallbacks = [] // 保存失败回调
    }

    resolve(value) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行resolve,状态变为fulfilled
        this.PromiseState = 'fulfilled'
        // 终值为传进来的值
        this.PromiseResult = value
        // 执行保存的成功回调
+       while (this.onFulfilledCallbacks.length) {
    +       this.onFulfilledCallbacks.shift()(this.PromiseResult)
+       }
    }

    reject(reason) {
        // state是不可变的
        if (this.PromiseState !== 'pending') return
        // 如果执行reject,状态变为rejected
        this.PromiseState = 'rejected'
        // 终值为传进来的reason
        this.PromiseResult = reason
        // 执行保存的失败回调
+       while (this.onRejectedCallbacks.length) {
    +       this.onRejectedCallbacks.shift()(this.PromiseResult)
+       }
    }
    
    then(onFulfilled, onRejected) {
        // 接收两个回调 onFulfilled, onRejected

        // 参数校验,确保一定是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

        if (this.PromiseState === 'fulfilled') {
            // 如果当前为成功状态,执行第一个回调
            onFulfilled(this.PromiseResult)
        } else if (this.PromiseState === 'rejected') {
            // 如果当前为失败状态,执行第二哥回调
            onRejected(this.PromiseResult)
+       } else if (this.PromiseState === 'pending') {
+           // 如果状态为待定状态,暂时保存两个回调
+           this.onFulfilledCallbacks.push(onFulfilled.bind(this))
+           this.onRejectedCallbacks.push(onRejected.bind(this))
+       }

    }

完整代码为:

看下是否能够实现定时器的功能:

2.3 链式调用

then支持链式调用,下一次then执行受上一次then返回值的影响,给大家举个例子:

根据上文,可以得到:

  1. then方法本身会返回一个新的Promise对象;
  2. 如果返回值是promise对象,返回值为成功,新promise就是成功;
  3. 如果返回值是promise对象,返回值为失败,新promise就是失败;
  4. 如果返回值非promise对象,新promise对象就是成功,值为此返回值;

then是Promise上的方法,那如何实现then完还能再then呢?

then执行后返回一个Promise对象就行了,就能保证then完还能继续执行then。

前端异步编程规范

我们要在then里面调用 onFulfilled 和 onRejected 的方法,此时我们创建一个 resolvePromise 作为thenPromise 和 onfulfilled/onRejected 建立的关联。

使用 resolvePromise 调用 onfulfilled/onRejected 方法,需要把 onfulfilled/onRejected 作为参数传进去,onfulfilled(this.PromiseResult)/onRejected(this.PromiseResult)调用后再判断其得到的结果,根据结果继续往下走。

  • 如果也是一个Promise对象,就去执行then;
  • 如果只是一个值,就resolve;
  • 如果异常,就执行reject;

对于resolvePromise来说,会存在一些边界条件,比如返回自身时,可能会造成死循环,所以要抛出异常:

then的完整代码为:

测试一下:

2.4 执行顺序

这里需要了解,then方法是微任务,微任务应在宏任务执行后才执行(后续文章会讲到)。

我们来看一下Promise的例子:

这里为了实现类似的功能,我们先使用setTimeout代替(setTimeout为宏任务,此处主要跟在全局上的console对比)

至此,完整的代码为:

class MyPromise {
// 构造方法
constructor(executor) {
// 初始化值
this.initValue()
// 初始化this指向
this.initBind()
try {
// 执行传进来的函数
executor(this.resolve, this.reject)
} catch (e) {
// 捕捉到错误直接执行reject
this.reject(e)
}
}
// 初始化值
initValue() {
this.PromiseResult = null // 终值
this.PromiseState = 'pending' // 状态
this.onFulfilledCallbacks = [] // 保存成功回调
this.onRejectedCallbacks = [] // 保存失败回调
}
// 初始化this
initBind() {
this.resolve = this.resolve.bind(this)
this.reject = this.reject.bind(this)
}
resolve(value) {
// state是不可变的
if (this.PromiseState !== 'pending') return
// 如果执行resolve,状态变为fulfilled
this.PromiseState = 'fulfilled'
// 终值为传进来的值
this.PromiseResult = value
// 执行保存的成功回调
while (this.onFulfilledCallbacks.length) {
this.onFulfilledCallbacks.shift()(this.PromiseResult)
}
}
reject(reason) {
// state是不可变的
if (this.PromiseState !== 'pending') return
// 如果执行reject,状态变为rejected
this.PromiseState = 'rejected'
// 终值为传进来的reason
this.PromiseResult = reason
// 执行保存的失败回调
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(this.PromiseResult)
}
}
// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {
// 参数校验,确保一定是函数
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
onRejected = typeof onRejected === 'function' ? onRejected : reason => {
throw reason
}
var thenPromise = new MyPromise((resolve, reject) => {
const resolvePromise = cb => {
setTimeout(() => {
try {
const x = cb(this.PromiseResult)
// 不能返回自身哦
if (x === thenPromise && x) {
throw new Error('不能返回自身。。。')
}
// 如果返回值是Promise
if (x instanceof MyPromise) {
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
throw new Error(err)
}
})
}
if (this.PromiseState === 'fulfilled') {
// 如果当前为成功状态,执行第一个回调
resolvePromise(onFulfilled)
} else if (this.PromiseState === 'rejected') {
// 如果当前为失败状态,执行第二个回调
resolvePromise(onRejected)
} else if (this.PromiseState === 'pending') {
// 如果状态为待定状态,暂时保存两个回调
// 如果状态为待定状态,暂时保存两个回调
this.onFulfilledCallbacks.push(resolvePromise.bind(this, onFulfilled))
this.onRejectedCallbacks.push(resolvePromise.bind(this, onRejected))
}
})
// 返回这个包装的Promise
return thenPromise
}
}

测试一下:

let p4 = new MyPromise((resolve, reject) => {
resolve(1)
}).then(res => console.log(res), err => console.log(err))
console.log(2)
// 输出顺序是 2 1

3.其它方法

3.1 all

Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例。

语法:

Promise.all([p1, p2, p3]);

我们来使用一下all,并打印一下它的返回结果。

let p1 = new MyPromise((resolve, reject) => {
resolve('success')
});
let p2 = new MyPromise((resolve, reject) => {
resolve(100)
});
let p3 = new MyPromise((resolve, reject) => {
throw('error')
});
Promise.all([p1, p2, 2]).then(res => {
console.log(res)
})
// ['success', 100, 2]
Promise.all([p1, p2, p3]).then(res => {
console.log(res)
}, err => {
console.log(err)
})
// 'error'

作用:

  1. 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
  2. 如果所有Promise都成功,则返回成功结果数组;
  3. 如果有一个Promise失败,则返回这个失败结果;

如果我们想模拟封装一个这样的all呢?

  1. 声明两个变量:
  • result 用来存放结果;
  • count 用来计数;
  • 当 Promise 数组中的函数全部执行成功时,将result 返回;
  1. 依次调用 Promise 数组中的函数:
  • 如果是 Promise 函数,就交给then,执行成功就存放在 result 中,失败就直接 reject;
  • 如果不是 Promise 函数而是数值,就当作执行成功,直接把值存放在 result 中;
  1. 存储在 result 中后,每次需要判断一下,是否已经全部执行完成,若已全部执行完成,则将 result 返回;

all的封装代码:

const all = (promiseList) => {
let result = [];
let count = 0;
return new Promise((resolve, reject) => {
const addData = function (val, i) {
result[i] = val;
count++;
// 若全部执行完成,则将结果返回
if (count === promiseList.length) {
resolve(result)
}
}
promiseList.forEach((p, i) => {
if (p instanceof MyPromise) {
p.then(res => {
// 将结果存放在 result 中
addData(res, i)
}, err => {
// 直接 reject
reject(err)
})
} else {
// 将结果存放在 result 中
addData(p, i)
}
})
})
}

3.2 race

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

语法:

Promise.race([p1, p2, p3]);

我们来使用一下race,并看一下它的返回结果。

let p1 = new MyPromise((resolve, reject) => {
resolve('success')
});
let p4 = new MyPromise((resolve, reject) => {
reject('fail')
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
let p5 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success is 2s delay')
}, 2000)
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
Promise.race([p1, p4, p5]).then(res => {
console.log(res)
})
// 'success'

作用:

  1. 接收一个 Promise 数组,数组中如有非Promise项,则此项当做成功;
  2. 哪个 Promise 最快得到结果,就返回那个结果,无论成功失败;

如果我们想模拟封装一个这样的race呢?
思路基本和上面的 all 相同,甚至逻辑还比 all 要更简单。

因为 race 是抢占式的,所以依次调用 Promise 数组中的函数直接返回即可,先到先得。

  • 如果是 Promise 函数,就交给then,执行成功直接 resolve,失败就直接 reject;
  • 如果不是 Promise 函数而是数值,就当作执行成功,直接 resolve;

race的封装代码:

const race = (promiseList) => {
return new Promise((resolve, reject) => {
promiseList.forEach((promise, i) => {
if (promise instanceof MyPromise) {
promise.then(res => {
resolve(res)
}, err => {
reject(err)
})
} else {
resolve(promise)
}
})
})
}

3.3 allSettled

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

语法:

Promise.allSettled([p1, p2, p3]);

我们来使用一下allSettled,并打印一下它的返回结果。

let p1 = new MyPromise((resolve, reject) => {
resolve('success')
});
let p3 = new MyPromise((resolve, reject) => {
throw('error')
});
let p5 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success is 2s delay')
}, 2000)
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
Promise.allSettled([p1, p3, p5, 100]).then(res => {
console.log(res)
})

打印结果:
前端异步编程规范
作用:

  1. 接收一个 Promise 数组,数组中如有非Promise项,则此项当做成功;
  2. 把每一个 Promise 的结果,集合成数组后返回;

如果我们想模拟封装一个这样的allSettled呢?

思路和all是一样的,区别是:

  1. 返回的 result 是json数组,需要包含状态值:
  2. 依次调用 Promise 数组中的函数时,即使失败,也要把结果存放在 result 中,而不是直接 reject;

allSettled的封装代码:

const allSettled = (promiseList) => {
let result = [];
let count = 0;
return new Promise((resolve, reject) => {
const addData = (value, i, status) => {
result[i] = {
value,
status
}
count++;
if (count === promiseList.length) {
resolve(result);
}
}
promiseList.forEach((promise, i) => {
if (promise instanceof MyPromise) {
promise.then(res => {
addData(res, i, 'fulfilled')
}, err => {
// 失败也要存放在result中
addData(err, i, 'rejected')
})
} else {
addData(promise, i, 'fulfilled')
}
})
})
}

3.4 any

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。

语法:

Promise.any([p1, p2, p3]);

我们来使用一下any,并打印一下它的返回结果。

let p1 = new Promise((resolve, reject) => {
resolve('success')
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
let p6 = new Promise((resolve, reject) => {
reject('fail')
}).then(res => {
console.log(res);
}, err => {
console.log(err);
})
let p7 = new Promise((resolve, reject) => {
throw new Error('123')
})
Promise.any([p1, p5, p6]).then(res => {
console.log(res);  
}, err => {
console.log(err);
})
// 打印结果:
// success
// fail
// undefined
Promise.any([p5, p6]).then(res => {
console.log(res);
}, err => {
console.log(err);
})
// 打印结果:
// undefined

作用:与Promise.all()作用正相反。

1. 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
2. 如果有一个Promise成功,则返回这个成功结果;
3. 如果所有Promise都失败,则报错;

如果我们想模拟封装一个这样的any呢?

  1. 声明 count 变量用来统计失败的次数,如果全部失败,则 reject err:
  2. 对于执行成功的 Promise 函数,和race一样使用抢占式机制,直接返回,先到先得;

any的封装代码:

const any = (promiseList) => {
let count = 0;
return new MyPromise((resolve, reject) => {
promiseList.forEach((promise) => {
promise.then(res => {
resolve(res)
}, err => {
count++;
if (count === promiseList.length) {
reject('All promises are rejected')
}
})
})
})
}

三.Promise A+规范

上文我们实现了简版的 Promise,接下来看下标准的 Promise/A+ 的规范:

对照的翻译如下:

一个开放、健全且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。

一个 promise 表示异步操作的最终结果。与 promise 进行交互的主要方式是通过它的方法 then。该方法通过注册回调来得到这个 promise 的最终 value ,或者为什么这个 promise 不能被 fulfilled 的 reason 。

该规范详细说明了 then 方法的行为,提供了一个可互操作的基础,因此所有符合 Promises/A+ 的 promise 实现都可以依赖该基础。尽管 Promises/A+ 组织可能偶尔会通过向后兼容的微小更改来修改此规范,以解决新发现的情况,但我们只有在仔细考虑、讨论和测试后才会进行大的或向后不兼容的更改。因此, 该规范应该被认为是十分稳定的 。

从历史上看, Promises/A+ 阐明了早期 Promises/A proposal 的条款,并将部分事实上已经实现的拓展涵盖其中,以及对某些未指定或者有问题的部分省略。

最后,Promises/A+ 规范的核心不包括:如何 create 、fulfill 或 reject promises,而是选择专注于提供可互操作的 then 方法。不过伴随规范的未来工作可能会涉及这些主题。

这里可以看到,Promises/A+ 规范目前的核心是规范 then 方法,并没有对如何实现 promise 以及如何改变 promise 的状态进行限制。

1.术语

  1. prmoise 是一个拥有符合本规范的 then 方法的对象或者函数;
  2. thenable 是一个定义了 then 方法的对象或者函数;
  3. value 是 JavaScript 的任意合法值(包括 undefined, thenable, promise);
  4. exception 是一个用 throw 语句抛出的 value ;
  5. reason 是一个表示 promise 被 rejected 的 value ;

2.要求

2.1 promise 的状态

pormise 必须是以下三个状态之一: pending, fulfilled, rejected

  • 当 promise 处于 pending 状态时:

    • 可以转换到 fulfilled 或 rejected 状态;
  • 当 promise 处于 fulfilled 状态时:

    • 不能转换到其他状态;
    • 必须有一个 value ,并且不能改变;
  • 当 promise 处于 rejected 状态时:

    • 不能转换到其他状态;
    • 必须有 reason ,并且不能改变;

2.2 then方法

promise 必须提供一个 then 方法,能由此去访问当前或最终的 value 或者 reason 。

pormise 的 then 方法, 接受两个参数:

promise.then(onFulfilled, onRejected)
  • onFulfilled 和 onRejected 都是可选参数:

    • 如果 onFulfilled 不是函数,则忽略;
    • 如果 onRejected 不是函数,则忽略;
  • 如果 onFulfilled 是一个函数:

    • 它必须在 promise 被 fulfilled 后,以 promise 的 value 作为第一个参数调用;
    • 它不能在 promise 被 fulfilled 之前调用;
    • 它不能被调用多次;
  • 如果 onRejected 是一个函数:

    • 它必须在 promise 被 rejected 后,以 promise 的 reason 作为第一个参数调用;
    • 它不能在 promise 被 rejected 之前调用;
    • 它不能被调用多次;
    • execution context 栈(即 执行上下文栈)只包含平台代码之前, onFulfilled 或者 onRejected 不能被调用 (译者注: 异步执行回调);
  • onFulfilled 或者 onRejected 必须以函数形式调用(即不能有this值);

  • then 方法可以被同一个 promise 调用多次:

    • 如果或者当 promise 处于 fulfilled 状态, 所有自己的 onFulfilled 回调函数,必须要按照 then 注册的顺序被调用;
    • 如果或者当 promise 处于 rejected 状态, 所有自己的 onRejected 回调函数,必须要按照 then 注册的顺序被调用;
  • then 方法必须要返回 promise:

promise2 = promise1.then(onFulfilled, onRejected);
  • 如果 onFulfilled 或者 onRejected 返回一个值 x ,则执行 Promise Resolution Procedure:[[Resolve]](promise2, x)
  • 如果 onFulfilled 或者 onRejected 抛出异常 e , promise2 必须以 e 作为 reason ,转到 rejected 状态;
  • 如果 onFulfilled 不是函数,并且 promise1 处于 fulfilled 状态 ,则 promise2 必须以与 promise1 同样的 value 被 fulfilled;
  • 如果 onRejected 不是函数,并且 promise1 处于 rejected 状态 ,则 promise2 必须以与 promise1 同样的 reason 被 rejected;

2.3 Promise Resolution Procedure

Promise Resolution Procedure 是一个抽象操作。它以一个 promise 和一个 value 作为输入,记作:[[Resolve]](promise, x) 。 如果 x 是一个 thenable , 它会尝试让 promise 变成与 x 的一样状态 ,前提 x 是一个类似的 promise 对象。否则,它会让 promise 以 x 作为 value 转为 fulfilled 状态。

这种对 thenables 的处理允许不同的 promise 进行互操作,只要它们暴露一个符合 Promises/A+ 的 then 方法。它还允许 Promises/A+ 实现使用合理的 then 方法“同化”不一致的实现。

[[Resolve]](promise, x) 执行以下步骤:

  • 如果 promise 和 x 引用的是同一个对象,则以一个 TypeError 作为 reason 让 promise 转为 rejeted 状态;

  • 如果 x 也是一个 promise ,则让 promise 接受它的状态:

    • 如果 x 处于 pending 状态,promise 必须保持 pending 状态,直到 x 变成 fulfilled 或者 rejected 状态,promise 才同步改变;
    • 如果或者当 x 处于 fulfilled 状态, 以同样的 value 让 promise 也变成 fulfilled 状态;
    • 如果或者当 x 处于 rejected 状态, 以同样的 reason 让 promise 也变成 rejected 状态;
  • 如果 x 是一个对象或者函数:

    • 令 then 等于 x.then;

    • 如果读取 x.then 抛出异常 e , 以 e 作为 reason 让 promise 变成 rejected 状态;

    • 如果 then 是一个函数,以 x 作为 this 调用它,传入第一个参数 resolvePromise , 第二个参数 rejectPromise :

      • 如果 resolvePromise 被传入 y 调用, 则执行 [[Resolve]](promise, y)

      • 如果 rejectedPromise 被传入 r 调用,则用,r 作为 reason 让 promise 变成 rejected 状态;

      • 如果 resolvePromise 和 rejectPromise 都被调用了,或者被调用多次了。只有第一次调用生效,其余会被忽略;

      • 如果调用 then 抛出异常 e:

        • 如果 resolvepromise 或 rejectPromise 已经被调用过了,则忽略它;
        • 否则, 以 e 作为 reason 让 promise 变成 rejected 状态;
      • 如果 then 不是一个函数,以 x 作为 value 让 promise 变成 fulfilled 状态;

  • 如果 x 不是对象或函数, 以 x 作为 value 让 promise 变成 fulfilled 状态;

如果一个 promise 被一个循环的 thenable 链中的对象 resolved,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励实现者检测这样的递归是否存在,并且以 TypeError 作为 reason 拒绝 promise。

四.async/await

1. 介绍

ES2017 标准引入了async函数,使得异步操作变得更加方便。

先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。

写一个async 函数:

async function fn() {
console.log('hello world')
}

语法很简单,就是在函数前面加上async关键字,来表示它是异步的,那怎么调用呢?async 函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了,为了表示它没有阻塞它后面代码的执行,我们在async 函数调用之后加一句console.log;

async function fn() {
console.log('hello world')
}
fn()
console.log(11111);

前端异步编程规范

async关键字差不多了,我们再来考虑await关键字,await是等待的意思,那么它等待什么呢?它后面跟着什么呢?其实它后面可以放任何表达式,不过我们更多的是放一个返回promise对象的表达式。

注意:await关键字只能放到async函数里面。

现在写一个函数,让它返回promise 对象,该函数的作用是2s 之后让数值乘以2:

function request(num) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(num * 2)
}, 2000)
})
}

现在再写一个async函数,从而可以使用await关键字, await后面放置的就是返回 promise 对象的一个表达式,所以它后面可以写 request 函数的调用:

async function fn() {
let res = await request(100);
console.log(res);
}
fn()  // 2s之后输出 200

现在我们看看代码的执行过程,调用 fn 函数,它里面遇到了await, await 表示等一下,代码就暂停到这里,不再向下执行了,它等什么呢?等后面的 promise 对象执行完毕,然后拿到 promise resolve 的值并进行返回,返回值拿到之后,它继续向下执行。

就这一个函数,我们可能看不出 async/await 的作用,如果我们要计算3个数的值,然后把得到的值进行输出呢?

async function fn() {
let res1 = await request(100);
let res2 = await request(200);
let res3 = await request(300);
console.log(res1 + res2 + res3);
}
fn();   // 6s之后输出 1200

至此,我们可以看到,写异步代码就像写同步代码一样了,真的方便多了。

2. 详解

从上面的讲义中,我们知道了async/await的用处:用同步方式,执行异步操作

我们用 request 函数模拟接口请求:

function request(num) { 
return new Promise(resolve => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}

现在有一个需求:先请求完接口1,再拿接口1返回的数据,去当做接口2的请求参数;再拿接口2返回的数据,去当做接口3的请求参数;

request(1).then(res1 => {
// 2s之后先打印2
console.log(res1);
request(res1).then(res2 => {
// 4s之后打印4
console.log(res2);
request(res2).then(res3 => {
// 6s之后打印8
console.log(res3);
})
})
})

如果嵌套的多了,这个时候就可以用async/await来解决了:

async function fn() {
const res1 = await request(1);
console.log(res1);
const res2 = await request(res1);
console.log(res2);
const res3 = await request(res2)
console.log(res3);
}
fn();

async函数中,await规定了异步操作只能一个一个排队执行,从而达到用同步方式,执行异步操作的效果。

注意:await只能在async函数中使用

刚刚上面的例子await后面都是跟着异步操作 Promise,那如果不接 Promise呢?

function request(num) { 
// 去掉Promise
setTimeout(() => {
console.log(num * 2)
}, 1000)
}
async function fn() {
await request(1) // 2
await request(2) // 4
// 1秒后执行完  同时输出
}
fn()

可以看出,如果await后面接的不是 Promise 的话,其实是达不到类似同步的效果的。

什么是async

async是一个位于 function 之前的前缀,只有async函数中,才能使用await。那async执行完是返回是什么?

async function fn() {}
console.log(fn) // [AsyncFunction: fn]
console.log(fn()) // Promise {<fulfilled>: undefined}

可以看出,async函数执行完会自动返回一个状态为 fulfilled 的 Promise,也就是成功状态,但是值却是undefined,那要怎么才能使值不是 undefined 呢?只要函数有 return 返回值就行了。

async function fn(num) {
return num
}
console.log(fn) // [AsyncFunction: fn]
console.log(fn(10)) // Promise {<fulfilled>: 10}
fn(10).then(res => console.log(res)) // 10

3.总结

  • await只能在async函数中使用,不然会报错;
  • async函数返回的是一个 Promise 对象,有无值看有无 return 值;
  • await后面最好是接 Promise,如果await后面是 promise 对象会造成异步函数停止执行并且等待 promise 的解决,如果await后面是正常的表达式则立即执行;
  • async/await作用是用同步方式,执行异步操作;

4.语法糖

Q:async/await是一种语法糖,那么什么是语法糖呢?

A:语法糖是简化代码的一种方式,用其他方式也能达到同样的效果,但写法可能没有这么便利。套了一层外衣
,ES6的class也是语法糖,因为其实用普通 function 也能实现声明构造函数的同样效果。

回归正题,async/await是一种语法糖,用到的是ES6里的迭代函数——generator函数。

五.generator

1.介绍

generator函数跟普通函数在写法上的区别就是,多了一个星号*,并且只有在generator函数中才能使用yield,而yield相当于generator函数执行的中途暂停点。比如下方有3个暂停点,而怎么才能暂停后继续走呢?那就得使用到next方法,next方法执行后会返回一个对象,对象中有valuedone两个属性:

  • value:暂停点后面接的值,也就是yield后面接的值;
  • done:generator函数是否已走完,未走完为false,走完则为true;
// 使用 * 声明迭代器
function* gen() {
yield 1
yield 2
yield 3
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: undefined, done: true }

可以看到最后一个执行结果的 value 是 undefined,这取决于声明的generator函数有无返回值。

我们对上面的例子稍加调整,return一个返回值,再来看下执行结果:

function* gen() {
yield 1
yield 2
yield 3
return 4
}
const g = gen()
console.log(g.next()) // { value: 1, done: false }
console.log(g.next()) // { value: 2, done: false }
console.log(g.next()) // { value: 3, done: false }
console.log(g.next()) // { value: 4, done: true }

可以看到,现在最后一个执行结果的value就是我们return的值。

1.1 yield后接函数

yield后面接函数的话,到了对应暂停点yield,会马上执行此函数,并且该函数的执行返回值,会被当做此暂停点对象的value

// yield后面的函数
function fn(num) {
console.log(num)
return num
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next())
// 1
// { value: 1, done: false }
console.log(g.next())
// 2
//  { value: 2, done: false }
console.log(g.next())
// { value: 3, done: true }

1.2 yield后接promise

我们再来看一下yield后面如果接的是 promise 函数,会有怎样的效果:

// yield后面的romise
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 { <pending> }, done: false }
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }

如果想获取的是两个 Promise 的结果1和2,可以使用 Promise 的then:

const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
console.log(res1) // 1秒后输出 1
const next2 = g.next()
next2.value.then(res2 => {
console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
console.log(res2) // 2秒后输出 2
const next3 = g.next()
console.log(next3) // 2秒后输出 { value: 3, done: true }
})
})

1.3 next函数传参

generator函数可以用next方法来传参,并且可以通过yield来接收这个参数。

但是需要注意两点:

  1. 第一次next传参是没用的,只有从第二次开始next传参才有用;
  2. 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(11111))
// { value: 1, done: false }
console.log(g.next(22222))
// 22222
//  { value: 2, done: false }
console.log(g.next(33333))
// 33333
// { value: 3, done: true }

1.4 Promise&next传参

根据上文可以知道:

  • yield后面可以接Promise;
  • next函数可以传参;

所以一起使用时的效果为:

function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后同时输出 { value: Promise { 2 }, done: false }
console.log(res1) // 1秒后同时输出 2
const next2 = g.next(res1) // 传入上次的res1
next2.value.then(res2 => {
console.log(next2) // 2秒后同时输出 { value: Promise { 4 }, done: false }
console.log(res2) // 2秒后同时输出 4
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
console.log(next3) // 3秒后同时输出 { value: Promise { 8 }, done: false }
console.log(res3) // 3秒后同时输出 8
// 传入上次的res3
console.log(g.next(res3)) // 3秒后同时输出 { value: 8, done: true }
})
})
})

2.实现 async/await

上方的generator函数的Promise&next传参,就很像async/await了,区别在于

  1. gen 函数执行返回值不是 Promise,asyncFn 执行返回值是 Promise;
  2. gen 函数需要执行相应的操作,才能等同于 asyncFn 的排队效果;
  3. gen 函数执行的操作是不完善的,因为并不确定有几个 yield,不确定会嵌套几层;

针对这种情况,可以通过高阶函数(HOC)封装:

function highorderFn(函数) {
// 一系列处理
return 函数
}

根据上述代码,可以封装一个高阶函数,接收一个generator函数,并经过处理,返回一个具有async函数功能的函数:

function generatorToAsync(generatorFn) {
// 经过一系列处理
return 具有async函数功能的函数
}

2.1 返回值promise

function* gen() {
}
const asyncFn = generatorToAsync(gen)
console.log(asyncFn()) // 这里期望输出 Promise

这里需要针对generatorToAsync进行处理:

function* gen() {
}
function generatorToAsync (generatorFn) {
return function () {
return new Promise((resolve, reject) => {
})
}
}
const asyncFn = generatorToAsync(gen)
console.log(asyncFn()) // Promise

2.2 结合上述代码

把之前的处理代码,加入generatorToAsync函数中。

function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
function* gen() {
const num1 = yield fn(1)
const num2 = yield fn(num1)
const num3 = yield fn(num2)
return num3
}
function generatorToAsync(generatorFn) {
return function () {
return new Promise((resolve, reject) => {
const g = generatorFn()
const next1 = g.next()
next1.value.then(res1 => {
const next2 = g.next(res1) // 传入上次的res1
next2.value.then(res2 => {
const next3 = g.next(res2) // 传入上次的res2
next3.value.then(res3 => {
// 传入上次的res3
resolve(g.next(res3).value)
})
})
})
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res)) // 3秒后输出 8

到这里,就已经实现了async/await的初始功能了:

async function asyncFn() {
const num1 = await fn(1)
const num2 = await fn(num1)
const num3 = await fn(num2)
return num3
}
asyncFn().then(res => console.log(res)) // 3秒后输出 8

2.3 支持多个await方法

因为async中可以支持若干个await,await的个数是不确定的。同样类比,generator函数中,也可能有2个yield,3个yield,5个yield,所以需要对上述代码进行改造:

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
} 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有可能是成功或者失败
return Promise.resolve(value).then(val => go('next', val), err => go('throw', err))
}
}
go("next") // 第一次执行
})
}
}
const asyncFn = generatorToAsync(gen)
asyncFn().then(res => console.log(res))

2.4 测试结果

  • async/await:
async function asyncFn() {
const num1 = await fn(1)
console.log(num1) // 2
const num2 = await fn(num1)
console.log(num2) // 4
const num3 = await fn(num2)
console.log(num3) // 8
return num3
}
const asyncRes = asyncFn()
console.log(asyncRes) // Promise
asyncRes.then(res => console.log(res)) // 8
  • generatorToAsync:
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

到这里,我们就基本完成了async/await的封装。

2.5 更多资料

Generator 具体使用场景

原文链接:https://juejin.cn/post/7314519123177668620 作者:Nunumaymax

(0)
上一篇 2023年12月21日 上午11:11
下一篇 2023年12月21日 下午4:00

相关推荐

发表评论

登录后才能评论