面试必考 - 手写 Promise, 由浅入深(附源码)

1,343 阅读17分钟

前言

Hello 大家好!我是壹甲壹!

相信大家无论在前端还是后端开发工作中,都接触并使用过 Promise ,本文将带领大家「step-by-step」实现一个符合 Promises/A+ 规范的 Promise,同时探索 Promise 中的一些方法以及第三方扩展如何实现的。

通过阅读本篇文章你可以学习到:

  • 手写实现符合规范的 Promise
  • 使用 promises-aplus-tests 进行规范测试
  • 掌握 Promise.allPromise.race, Promise.resolve, Promise.reject 等实现原理
  • 掌握 Node 中对 Promise 的一些扩展

在正式进入正题之前,为了更好地理解和掌握 Promise ,我们先来介绍一些与 Promise 相关的基础知识。

一、什么是异步

1.1 JS 中为什么存在异步

大家应该都知道,JS 属于单线程语言,所谓单线程,就是一次只能干一件事,其它事情只能在后面乖乖排队等待。

在浏览器中,页面加载过程中存在大量请求,当一个网络请求迟迟没有响应,页面将傻傻等着,不能处理其它事情。

因此,JS 中设计了异步,即发送完网络请求后就可以继续处理其它操作,而网络请求返回的数据,可通过回调函数来接收处理,这样就保证了页面的正常运行。

1.2 异步解决方案

先看下面一段 Node 代码

var fs = require('fs')
fs.readFile('data.json', (err, data) => {
    console.log(data.toString())
})

fs.readFile 方法的第二个参数是个函数,函数并不会立即执行,而是等到读取的文件结果出来才执行,这是函数就是回调函数,即 callback

1.3 回调地狱

处理多个异步请求,并且一个一个嵌套时,就容易产生回调地狱。看下面一段 Node 代码

const fs = require('fs')
fs.readFile('data1.json', (err, data1) => {
    fs.readFile('data2.json', (err, data2) => {
        fs.readFile('data3.json', (err, data3) => {
            fs.readFile('data4.json', (err, data4) => {
                console.log(data4.toString())
            })
        })
    })
})

使用 Promise 改写

const fs = require('fs')
const readFilePromise = (file) => {
 return new Promise((resolve, reject) => {
   fs.readFile(file, (err, data) => {
     if (err) {
       reject(err)
      }
      resolve(data)
    })
  })
}
readFilePromise('data1.json')
.then(data1 => {
 return readFilePromise('data2.json')
}).then(data2 => {
 return readFilePromise('data3.json')
}).then(data3 => {
 return readFilePromise('data4.json')
}).then(data4 => {
  console.log(data4.toString())
}).catch(err => {
 console.log(err)
})

「思考题」:Promise 真的取代 callback 了嘛?

Promise 只是对于异步操作代码的可读性的一种变化,没有改变 JS 中异步执行的本质,也无法取代 callback 在 JS 中的存在。同时,在 Promise 中,也存在着 callback 的使用,实例的 then() 的参数分别是执行成功、失败的函数,也就是 callback 回调函数。

二、Promise 的实现

本篇文章对应的项目地址: github.com/Yangjia23..…

2.1 基本实现

2.1.1 executor 执行器

首先,Promise 是个类,需要使用 new 来创建实例

  • new Promise((resolve, reject) => {}) 传入的参数是个函数,被称为 executor 执行器,默认会立即执行
  • executor 执行时会传入两个参数 resolve, reject ,分别是执行成功函数、执行失败函数
  • resolve, reject 两个执行函数不属于 Promise 类上的静态属性,也不是实例上的方法,而是一个普通函数
class Promise {
 constructor (executor) {
    // 成功
    const resolve = () => {}
    // 失败
    const reject = () => {}
    // 立即执行
   executor(resolve, reject)
  }
}

2.1.2 三种状态

关于 Promise 状态

  • promise 有三种状态:等待 (pending)已成功 (fulfilled)已失败(rejected),默认状态为 pending

  • promise 的状态只能从 pending 转换成 fulfilledrejected 两种状态变化

了解promise状态更多内容,请查看Promises/A+规范: promise-states

以 readFilePromise 为例

  • 读取文件成功时,会调用resolve函数,传入读取的内容,表示执行成功,此时的状态应是 fulfilled 成功态
  • 读取文件失败,会调用 reject 函数,传入失败的原因,表示执行失败,此时的状态应是 fulfilled 失败态
  • 读取的文件内容或失败的原因需要保存,分别使用 valuereason 存储
const ENUM = {
 PENDING: 'pending',
  FULFILLED: 'fulfilled',
  REJECTED: 'rejected'
}
class Promise {
 constructor (executor) {
    this.status = ENUM.PENDING // 默认状态
    this.value = undefined // 保存执行成功的值
    this.reason = undefined // 保存执行失败的原因
    // 成功
    const resolve = (value) => {
     if (this.status === ENUM.PENDING) {
       this.status = ENUM.FULFILLED
        this.value = value
      }
    }
    // 失败
    const reject = (reason) => {
     if (this.status === ENUM.PENDING) {
       this.status = ENUM.REJECTED
        this.reason = reason
      }
    }
    // 立即执行
   executor(resolve, reject)
  }
}

2.1.3 异常捕获

由于 executor 执行器是由用户传入的,在执行过程中可能出现错误,此时需要使用 try...catch... 进行异常捕获,当发生错误后,直接调用 reject 抛出错误

class Promise {
 constructor (executor) {
    // ....
    // 异常捕获
   try{
      // 立即执行
     executor(resolve, reject)
    } catch (e) {
     reject(e)
    }
  }
}

2.1.4 实现 then 方法

调用 new Promise() 返回的实例上有个 then 方法,then 方法需要用户提供两个参数,分别是执行成功后对应的成功回调 onFulfilled 和执行失败后对应的失败回调 onRejected

  • 当状态变成 fulfilled,会调用 onFulfilled 方法,并传入成功的值 this.value
  • 当状态变成 rejected,会调用 onRejected 方法,并传入失败的原因 this.reason
class Promise {
    constructor(executor) {
        // ...
    }
    then(onFulfilled, onRejected) {
        if (this.status == ENUM.FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.status == ENUM.REJECTED) {
            onRejected(this.reason)
        }
    }
}

executor 中执行的是异步操作时,执行 then 方法时状态还是 pending

异步操作例如 setTimeout属于宏任务,而 promise.then 属于微任务, 微任务先于宏任务执行,所以then方法执行时,promise的状态还是 pending

同时实例promise可以多次调用 then 方法,所以,需要将所有 then 方法中的回调函数搜集保存好,当异步操作完成后,再执行保存的回调函数(基于发布订阅模式

const promise = new Promise((resolve, reject) => {
   setTimeout(() => {}, 2000)
})

promise.then(data => {//...}, err => {})

promise.then(data => {//...}, err => {})

所以,接下来需要实现的是

  • 创建两个队列 onResolvedCallbacksonRejectedCallbacks,分别存放 then 方法中对应的成功回调和失败回调
  • 当异步操作成功时,调用 resolve 函数时,执行 onResolvedCallbacks 队列中每个成功回调
  • 当异步操作失败时,调用 reject 函数时,执行 onRejectedCallbacks 队列中每个失败回调
class Promise {
    constructor(executor) {
        this.status = ENUM.PENDING
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = [] // 成功队列
        this.onRejectedCallbacks = [] // 失败队列
        // 成功回调
        const resolve = (value) => {
            if (this.status === ENUM.PENDING) {
                this.status = ENUM.FULFILLED
                this.value = value
                this.onResolvedCallbacks.forEach(cb => cb()) // 相对于发布
            }
        }
        // 失败回调
        const reject = (reason) => {
            if (this.status === ENUM.PENDING) {
                this.status = ENUM.REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach(cb => cb())
            }
        }
        // 立即执行
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        // ...
        if (this.status === ENUM.PENDING) {
           // 相对于订阅
            this.onResolvedCallbacks.push(() => {
                // todo...
                onFulfilled(this.value)
            });
            this.onRejectedCallbacks.push(() => {
                // todo...
                onRejected(this.reason);
            })
        }
    }
}

注意:在 then 方法中,并没有往队列中直接插入回调函数, 而是使用函数包装后再 push,是为了方便后续扩展 ( eg:获取并处理 onFulfilled() 的返回值)

到现在为止,实现了基础版 Promise , 但看着和之前的 callback 只是写法上不同,并没有体现出 Promise 的优势,接下来,继续探索 Promise 中的高级特性

2.2 高级特性

2.2.1 实现 then 链式调用

对于实例上的 then(onFulfilled, onRejected) 方法,其参数为成功、失败两个回调函数。总结出以下几个使用场景

  • 如果两个方法执行返回值是普通值,则会被传递到外层的下一个 then
  • 如果两个方法执行过程中抛出异常,则会在下一个 then 的失败回调中捕获异常
  • 当两个方法执行返回值是 promise, 那么会用该 promise 的状态作为结果 ( promise 的状态是“成功”,则会调用下一个 then 的成功回调;状态为“失败”则会调用下一个 then 的失败回调)
  • 错误处理,当发生错误时( then 中抛错或返回一个失败的 promise ),该错误会被最近的一个失败回调捕获,当该失败回调执行后,可以继续调用 then 方法

在 Promise 中,promise.then 链式调用的实现原理是通过返回一个新的 promise 来实现的

「思考题」为什么返回新的 promise, 而不是使用原来的 promise?

因为 promise 的状态一旦"成功"或"失败"了,就不能再改变了,所以只能返回新的 promise,这样才可以继续调用下一个then 中的成功/失败回调

接下来,需要实现以下几点

  • 调用 then 方法,创建一个新的 promise, 最后将这个新 promise 返回
  • 需要获取 then 方法中 onFulfilledonRejected 回调函数的返回值,通过新的 promise 传递到下一个 then 方法中
class Promise {
    //....
    then(onFulfilled, onRejected) {
       // 新的 promise
       let promise2 = new Promise((resolve, reject) => {})
        if (this.status == ENUM.FULFILLED) {
            let x = onFulfilled(this.value)
        }
        if (this.status == ENUM.REJECTED) {
            let x = onRejected(this.reason)
        }
        if (this.status === ENUM.PENDING) {
            this.onResolvedCallbacks.push(() => {
                let x = onFulfilled(this.value)
            });
            this.onRejectedCallbacks.push(() => {
                let x = onRejected(this.reason);
            })
        }
       return promise2
    }
}

现在,需要将回调函数执行的返回值 x 传递到下一个 then 方法中,是传递到下一个 then 方法中的成功回调,还是失败回调?需要根据 x 的值来判断。

  • x 是普通值,将通过 promise2 中的 resolve 传递给成功回调;
  • x 是个 Error,则通过 promise2 中的 reject 传递给失败回调;
  • 当然 x 也又有可能是个 promise 实例,所以都需要考虑到。

因为需要使用 promise2 中的 resolve, reject 传递 x (两个方法在外部无法获取到), 同时new Promise(executor) 时,executor 是立即执行,所以,将整个 then 方法中的逻辑放到 executor 函数中执行,就可以访问到 resolve, reject 方法了

class Promise {
    //....
    then(onFulfilled, onRejected) {
        // 新的 promise
        let promise2 = new Promise((resolve, reject) => {
            if (this.status == ENUM.FULFILLED) {
               // onFulfilled 执行可能报错,使用 try...catch...捕获
                try{
                 let x = onFulfilled(this.value)
                 resolve(x)
                } catch (e){
                 reject(e)
                }
            }
           // ...
        })
        return promise2
    }
}

因为返回值 x 存在多种情况, 所以将判断逻辑抽离到外部函数 resolvePromise

class Promise {
    //....
    then(onFulfilled, onRejected) {
        // 新的 promise
        let promise2 = new Promise((resolve, reject) => {
            if (this.status == ENUM.FULFILLED) {
               try{
                 let x = onFulfilled(this.value)
                 resolvePromise(x, promise2, resolve, reject)
                } catch (e){
                 reject(e)
                }
            }
            // ...
        })
        return promise2
    }
}
const resolvePromise = (x, promise2, resolve, reject) => {

}

相信仔细的小伙伴已经发现,在 new Promise 还没结束就访问 promise2 肯定会报错。只需将 resolvePromise 变成异步代码执行就可以访问到 promise2

//...
if (this.status == ENUM.FULFILLED) {
    setTimeout(() => {
        try {
            let x = onFulfilled(this.value)
            resolvePromise(x, promise2, resolve, reject)
        } catch (e) {
            reject(e)
        }
    }, 0)
}

接下来,需要实现 resolvePromise 方法了

2.2.2 resolvePromise 方法

resolvePromise 方法主要是用来解析 x 是否是promise, 按照 Promises/A+规范: the-promise-resolution-procedure 规定,分成以下几步

函数参数 resolvePromise(x, promise2, resolve, reject)

  • (1) 若 xpromise2 引用的是同一个对象,则直接报错。(示例代码如下)
let promise = new Promise((resolve, reject) => {})

let promise2 = promise.then(() => {
 return promise2 // x 代表了then中函数的返回值,也就是 promise2
})

promise2.then(() => {}, err=> {
 console.log('err:', err)
})

// err: TypeError: Chaining cycle detected for promise #<Promise> (循环引用了)
  • (2) 若 x 是一个普通值,直接通过 resolve 返回
  • (3) 若 x 是一个对象或者函数,判断 x 是否存在 then 方法,当存在 then 方法,表明 x 就是一个 promise,此时执行 then 方法
  • (4) 执行 then 方法时,有一个成功回调和一个失败回调,执行成功走成功回调,并传入成功结果 y;执行失败走失败回调,并传入失败原因 e, 使用 reject 返回
  • (5) 执行成功返回值 y 可能还是个 promise, 继续递归解析 y 的值
  • (6) then 的回调函数只能执行一次,要么成功,要么失败(设置标识符 called)
  • (7) 当 x 不存在 then 方法时,表明 x 是普通的对象,直接通过 resolve 返回
const resolvePromise = (x, promise2, resolve, reject) => {
  // (1)
  if (x === promise2) {
    reject(new TypeError(`TypeError: Chaining cycle detected for promise #<Promise>`))
  }
  
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    let called = false // (6)
   try {
     const then = x.then
      // (3)
      if (typeof then === 'function') {
       // (4)
        then.call(x, y => {
          // (5) y 可能是个 promise
          if (called) return
          called = true
         resolvePromise(y, promise2, resolve, reject)
        }, e => {
          if (called) return
          called = true
         reject(e)
        })
      } else {
        // (7)
       resolve(x)
      }
    } catch (e) {
      // then 执行过程出错,也不能继续向下执行
      if (called) return
      called = true
     reject(e)
    }
  } else {
   // (2)
    resolve(x)
  }
}

现在 resolvePromise 方法已经基本实现,其中还有以下几点需要说明

  1. 为啥需要判断 x 为函数?

因为 resolvePromise 需要兼容其他人写的 promise , 别人的 promise 可能就是一个函数

  1. 执行 const then = x.then 为啥需要使用 try...catch... 捕获异常 ?

因为可以使用 Object.definePropertiesProxy 改写 x.then 的返回值

  1. 执行 then 方法,为啥使用 call, 而不是直接执行 x.then() ?

可以复用上次取出来的then方法,避免二次调用 x.then()

2.2.3 值穿透

new Promise((resolve, reject) => {
 resolve(123)
}).then().then().then(data => {
 console.log('success:', data)
})
// success: 123

上面代码中的 123 是如何直接穿透到最后一个 then 方法中的呢?

Promises/A+规范: onFulfilled, onRejected are optional arguments , 规定 then 方法中的 onFulfilled, onRejected 是可选参数,所以我们需要提供一个默认值

class Promise {
    // ...
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
       onRejected = typeof onRejected === 'function' ? onRejected: e => {throw e}
        // ...
    }
}

通过给 onFulfilled, onRejected 设置默认值就可以实现值穿透。至此,已经实现 Promises/A+ 中规范的功能,可以对代码进行规范测试了

2.3 规范测试

规范测试,首先需要安装 promises-aplus-tests npm 包,同时需要在导出 Promise 前增加下面测试代码

class Promise {
 // ...
}
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    });
    return dfd;
}
module.exports = Promise;

安装依赖

npm install promises-aplus-tests -D

同时在 package.json 增加

"scripts": {
    "test": "promises-aplus-tests ./index.js"
 },

最后,运行 npm run test 就可以进行测试了,测试结果如下 截屏2020-07-02上午12.52.39.png

2.4 其它方法和属性

下面介绍的内容,并不是 Promises/A+ 中的规范,但我们也可以继续探索

2.4.1 catch 方法

实例上的 catch 方法用来捕获执行过程中产生的错误,同时返回值为 promise, 参数为一个失败回调函数,相对于执行 then(null, onRejected)

class Promise{
  // ...
 catch (onErrorCallback) {
   return this.then(null, onErrorCallback)
  }
}

2.4.2 finally 方法

finally 的参数是一个回调函数,无论 promise 是执行成功,还是失败,该回调函数都会执行。

应用场景有:页面异步请求数据,无论数据请求成功还是失败,在 finally 回调函数中都关闭 loading

同时,finally 方法有以下特点

  • 值穿透。可以将前面 promise 的值传递到下一个 then 方法中,或者将错误传递到下一个 catch 方法中
  • 等待执行。当 finally 回调函数返回一个新的 promise, finally 会等待该 promise 执行结束后才处理传值
  • 若该 promise 执行成功,finally 方法将不予理会执行结果,还是将上一个的结果传递到下一个 then
  • 若新的 promise 执行失败报错,finally 方法会将错误原因传递到下一个 catch 方法

下面是具体代码演示

// (1) 值穿透, 请注意 finally 的回调函数是不存在参数的
Promise.resolve(100).finally((data) => {
 console.log('finally: ', data)
}).then(data => {
  console.log('success: ', data)
}).catch(err => {
  console.log('error', err)
})
// finally:  undefined
// success:  100

// (2) 等待执行
// 返回一个执行成功的 promise, 但向下传递但还是上一次执行结果
Promise.resolve(100).finally(() => {
  return new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve(200)
    }, 1000)
  })
}).then(data => {
  console.log('success: ', data) // success:  100
}).catch(err => {
  console.log('error', err)
})

// 当 promise 执行失败,则将该 promise 执行结果向下传递
Promise.reject(100).finally(() => {
  return new Promise((resolve, reject) => {
   setTimeout(() => {
     reject(200)
    }, 1000)
  })
}).then(data => {
  console.log('success: ', data)
}).catch(err => {
  console.log('error', err) // error 200
})

在掌握了 finally 的用法后,继续探索如何实现它?

class Promise{
 finally (callback) {
   return this.then(value => {
     return Promise.resolve(callback()).then(() => value)
    }, err => {
     return Promise.resolve(callback()).then(() => {throw err})
    })
  }
}

2.4.3 静态方法

静态方法是那通过 Promise 来调用,而不是通过实例 promise 来调用的方法

  • Promise.resolve()、Promise.reject() 返回值:一个成功状态的 promise 、一个失败状态的 promise
class Promise{
  // ...
  // 成功状态
 static resolve(value){
   return new Promise((resolve, reject) => {
     resolve(value)
    })
  }
  // 失败状态
  static reject(reason){
   return new Promise((resolve, reject) => {
     reject(reason)
    })
  }
}

假设执行成功返回值 value 是个 promisePromise.resolve() 会对该 value 递归解析,直到该 promise 执行结束才会向下执行

class Promise{
 constructor() {
   //...
    const resolve = (value) => {
     if (value instanceof Promise) {
       // 递归解析, 直到 value 为普通值
        value.then(resolve, reject)
      }
      // ...
    }
    const reject = (err) => {
     // ...
    }
    //...
  }
}

现在,执行下面代码,就可以正常获取数据了

Promise.resolve(new Promise((resolve, reject) => {
 setTimeout(() => {
   resolve('hello')
  }, 2000)
})).then(data => {
 console.log(data) // hello
})
  • Promise.all()

解决并发问题,多个异步并发并获取最终的结果。

参数是一个 promise数组,当数组中每一项都执行成功,结果就是成功,反之,有一个失败,结果就是失败。

class Promise {
    static all(arrList) {
        if (!Array.isArray(arrList)) {
            const type = typeof arrList;
            return new TypeError(`TypeError: ${type} ${arrList} is not iterable`)
        }
        return new Promise((resolve, reject) => {
            const backArr = []
            const count = 0
            const processResultByKey = (value, index) => {
                backArr[index] = value
                if (++count === arrList.length) {
                    resolve(backArr)
                }
            }
            for (let i = 0; i < arrList.length; i++) {
                const item = arrList[i];
                if (item && item.then === 'function') {
                    item.then((value) => {
                        processResultByKey(value, i)
                    }, reject)
                } else {
                    processResultByKey(item, i)
                }
            }
        })
    }
}

⚠️注意:在 all 方法中,是通过 ++count === arrList.length (count 为计数器) 来判断是否全部执行完成,而不是使用 index === arrlist.length - 1 来判断,具体原因如下

// p1 为 promise 实例
Promise.all([1,2, p1, 4]).then(data => {})

// 当执行数组最后一项时,index === arrlist.length - 1 表达式成立,
// 就会执行 resolve 返回执行结果,
// 但此时的 p1 可能还没执行结束,所以使用计数器来判断
  • Promise.race()

all 方法不同的是,Promise.race 采用最先成功或最先失败的作为执行结果

class Promise {
    static race(arrList) {
        return new Promise((resolve, reject) => {
            for (let i = 0; i < arrList.length; i++) {
                const value = arrList[i];
                if (value && value.then === 'function') {
                    value.then(resolve, reject)
                } else {
                    resolve(value)
                }
            }
        })
    }
}

Promise.race 的主要应用场景如下

  • (基础)多个请求采取最快的 (eg: 小飞机的多个代理线路,哪条线路的响应速度最快,就使用哪条)
  • (高级)封装中断方法,中断 promise 的执行 (异步请求设置超时时间,当超时后,异步请求就会被迫失败)

原生的 promise 上并没有 abort (停止、中断) 方法,假设使用场景如下

const p1 = new Promise((resolve, reject) => {
 setTimeout(() => { // 模拟异步请求,5s 后返回
    resolve('hello')
  }, 5000)
})

const newP = wrap(p1)
setTimeout(() => { // 设置超时时间,超时后,调用 newP.abort
 newP.abort('请求超时了')
}, 4000)

newP.then(data => {}).catch(err => {})

newP1 是一个具有 abort 方法的 promise, 超时后就调用 newP.abort()

现在需要实现 wrap 封装方法,传入一个普通 promise 实例,返回一个具有 abort 方法的 promise 实例

const wrap = (promise) => {
  let abort
 let newPromise = new Promise((resolve, reject) => {
   abort = reject
  })
  let p = Promise.race([promise, newPromise])
  p.abort = abort
  return p
}

wrap 方法就是利用 Promise.race 采用最快的作为执行结果这一特性,来看 promise, newPromise 哪个最先执行,而 newPromise 的执行,是通过外部调用 abort 来实现的

三、Promise 的扩展

⚠️注意:以下对 Promise 的扩展仅适用于 Node 环境

3.1 promisify

功能:把 node 中的一个 api 转换成promise的写法, 以 fs.readFile 读取文件为例

常规写法

const fs = require('fs)
fs.readFile('./name.json', (err, data) => {})

缺点:回调地狱嵌套

改成 promisify 链式调用写法

const util = require('util')

const read = util.promisify(fs.readFile)
read('./name.json').then(data => console.log(data))

特点promisify 方法特点如下

  • 返回一个函数,函数执行后才返回 promise
const promisify = fn => {
 return (...args) => {
   return new Promise((resolve, reject) => {
     fn(...args, (err, data) => {
       if (err) reject(err)
        resolve(data)
      })
    })
  }
}
  • promisify 函数中,执行 fn 函数时,可以手动添加了回调函数是因为 node 中大部分的方法的回调都是这种格式

3.2 bluebird

promisify 方法每次只能修改一个方法,而第三方的库 bluebird 中实现了 promisifyAll 方法,可以将某个对象下所有的方法转换成 promise 写法

const fs = require('fs')
const bluebird = require('bluebird'); // 第三方库,需提前安装
const newFs = bluebird.promisifyAll(fs);
newFs.readFileAsync('./name.txt', 'utf-8').then(data => {}).catch(err => {})

promisifyAll() 特点如下

  • 函数参数为对象,会将对象上所有的方法,增加一个 Async** **后缀,变成 promise 写法
  • 并没有覆盖原方法,只是扩展
const promisifyAll = (target) {
 Reflect.ownKeys(target).forEach(key => {
    target[`${key}Async`] = promisify(target[key])
  })
  return target
}

Reflect 对象是 ES 中内置对象,它提供拦截 JavaScript 操作的方法 Reflect | MDN, 此处,也可使用 Object.keys() 。同时,使用了前面的 promisify 来改写方法

3.3 原生 Node 支持

目前,在高版本浏览器中,已经对 api 集成了 promise 的写法,使用如下

const fs = require('fs').promises
fs.readFile('./name.txt', 'utf-8').then(data => {})

正因为原生的支持,导致第三方的一些扩展不再流行

四、参考资源

五、后语

本文使用 mdnice 排版