面試必考 - 手寫 Promise, 由淺入深(附源碼)

前言

Hello 你們好!我是壹甲壹!前端

相信你們不管在前端仍是後端開發工做中,都接觸並使用過 Promise ,本文將帶領你們「step-by-step」實現一個符合 Promises/A+ 規範的 Promise,同時探索 Promise 中的一些方法以及第三方擴展如何實現的。node

經過閱讀本篇文章你能夠學習到:git

  • 手寫實現符合規範的 Promise
  • 使用 promises-aplus-tests 進行規範測試
  • 掌握 Promise.allPromise.race, Promise.resolve, Promise.reject 等實現原理
  • 掌握 Node 中對 Promise 的一些擴展

在正式進入正題以前,爲了更好地理解和掌握 Promise ,咱們先來介紹一些與 Promise 相關的基礎知識。github

1、什麼是異步

1.1 JS 中爲何存在異步

你們應該都知道,JS 屬於單線程語言,所謂單線程,就是一次只能幹一件事,其它事情只能在後面乖乖排隊等待。面試

在瀏覽器中,頁面加載過程當中存在大量請求,當一個網絡請求遲遲沒有響應,頁面將傻傻等着,不能處理其它事情。npm

所以,JS 中設計了異步,即發送完網絡請求後就能夠繼續處理其它操做,而網絡請求返回的數據,可經過回調函數來接收處理,這樣就保證了頁面的正常運行。json

1.2 異步解決方案

先看下面一段 Node 代碼後端

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

fs.readFile 方法的第二個參數是個函數,函數並不會當即執行,而是等到讀取的文件結果出來才執行,這是函數就是回調函數,即 callbackapi

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 回調函數。

2、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 來實現的

3、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 => {}) 複製代碼

正由於原生的支持,致使第三方的一些擴展再也不流行

4、參考資源

5、後語

本文使用 mdnice 排版

相關文章
相關標籤/搜索