面試官:「你能手寫一個 Promise 嗎」

本文首發於個人我的網站:litgod.net前端

關於手寫 Promise,想必你們都十分熟悉。基本上如今不論是大廠仍是小廠,手寫 promise 已經成爲了面試必考知識點。據說你還不太會?那麼走着,帶你從零開始解鎖 Promise!node

常見 Promise 面試題

首先,咱們以常見的 Promise 面試題爲切入點,咱們看看面試官們都愛考什麼:ios

  1. Promise 解決了什麼問題?
  2. Promise 的業界實現都有哪些?
  3. Promise 經常使用的 API 有哪些?
  4. 能不能手寫一個符合 Promise/A+ 規範的 Promise?
  5. Promise 在事件循環中的執行過程是怎樣的?
  6. Promise 有什麼缺陷,能夠如何解決?

這幾個問題由淺入深,咱們一個一個來看:git

Promise 出現的緣由 & 業界實現

在 Promise 出現之前,在咱們處理多個異步請求嵌套時,代碼每每是這樣的。。。github

let fs = require('fs')

fs.readFile('./name.txt','utf8',function(err,data){
  fs.readFile(data, 'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data);
    })
  })
})
複製代碼

爲了拿到回調的結果,咱們必須一層一層的嵌套,能夠說是至關噁心了。並且基本上咱們還要對每次請求的結果進行一系列的處理,使得代碼變的更加難以閱讀和難以維護,這就是傳說中臭名昭著的回調地獄~產生回調地獄的緣由歸結起來有兩點:面試

  1. 嵌套調用,第一個函數的輸出每每是第二個函數的輸入;
  2. 處理多個異步請求併發,開發時每每須要同步請求最終的結果。

緣由分析出來後,那麼問題的解決思路就很清晰了:ajax

  1. 消滅嵌套調用:經過 Promise 的鏈式調用能夠解決;
  2. 合併多個任務的請求結果:使用 Promise.all 獲取合併多個任務的錯誤處理。

Promise 正是用一種更加友好的代碼組織方式,解決了異步嵌套的問題。npm

咱們來看看上面的例子用 Promise 實現是什麼樣的:編程

let fs = require('fs')

function read(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) reject(err);
      resolve(data);
    })
  })
}

read('./name.txt').then((data)=>{
  return read(data) 
}).then((data)=>{
  return read(data)  
}).then((data)=>{
    console.log(data);
},err=>{
    console.log(err);
})
複製代碼

臃腫的嵌套變得線性多了有木有?沒錯,他就是咱們的異步神器 Promise!axios

讓咱們再次迴歸剛纔的問題,Promise爲咱們解決了什麼問題? 在傳統的異步編程中,若是異步之間存在依賴關係,就須要經過層層嵌套回調的方式知足這種依賴,若是嵌套層數過多,可讀性和能夠維護性都會變得不好,產生所謂的「回調地獄」,而 Promise 將嵌套調用改成鏈式調用,增長了可閱讀性和可維護性。也就是說,Promise 解決的是異步編碼風格的問題。 那 Promise 的業界實現都有哪些呢? 業界比較著名的實現 Promise 的類庫有 bluebird、Q、ES6-Promise。

從零開始,手寫 Promise

Promise/A+

咱們想要手寫一個 Promise,就要遵循 Promise/A+ 規範,業界全部 Promise 的類庫都遵循這個規範。

其實 Promise/A+ 規範對如何實現一個符合標準的 Promise 類庫已經闡述的很詳細了。每一行代碼在 Promise/A+ 規範中都有跡可循,因此在下面的實現的過程當中,我會盡量的將代碼和 Promise/A+ 規範一一對應起來。

下面開始步入正題啦~

基礎版 Promise

咱們先來回顧下最簡單的 Promise 使用方式:

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('成功了');
})

console.log("after new promise");

const p2 = p1.then(data => {
  console.log(data)
  throw new Error('失敗了')
})

const p3 = p2.then(data => {
  console.log('success', data)
}, err => {
  console.log('faild', err)
})
複製代碼

控制檯輸出:

"create a promise"
"after new promise"
"成功了"
"faild Error: 失敗了"
複製代碼
  • 首先咱們在調用 Promise 時,會返回一個 Promise 對象。
  • 構建 Promise 對象時,須要傳入一個 executor 函數,Promise 的主要業務流程都在 executor 函數中執行。
  • 若是運行在 excutor 函數中的業務執行成功了,會調用 resolve 函數;若是執行失敗了,則調用 reject 函數。
  • Promise 的狀態不可逆,同時調用 resolve 函數和 reject 函數,默認會採起第一次調用的結果。

以上簡單介紹了 Promise 的一些主要的使用方法,結合 Promise/A+ 規範,咱們能夠分析出 Promise 的基本特徵:

  1. promise 有三個狀態:pendingfulfilled,or rejected;「規範 Promise/A+ 2.1」
  2. new promise時, 須要傳遞一個executor()執行器,執行器當即執行;
  3. executor接受兩個參數,分別是resolvereject
  4. promise 的默認狀態是 pending
  5. promise 有一個value保存成功狀態的值,能夠是undefined/thenable/promise;「規範 Promise/A+ 1.3」
  6. promise 有一個reason保存失敗狀態的值;「規範 Promise/A+ 1.5」
  7. promise 只能從pendingrejected, 或者從pendingfulfilled,狀態一旦確認,就不會再改變;
  8. promise 必須有一個then方法,then 接收兩個參數,分別是 promise 成功的回調 onFulfilled, 和 promise 失敗的回調 onRejected;「規範 Promise/A+ 2.2」
  9. 若是調用 then 時,promise 已經成功,則執行onFulfilled,參數是promisevalue
  10. 若是調用 then 時,promise 已經失敗,那麼執行onRejected, 參數是promisereason
  11. 若是 then 中拋出了異常,那麼就會把這個異常做爲參數,傳遞給下一個 then 的失敗的回調onRejected

按照上面的特徵,咱們試着勾勒下 Promise 的形狀:

// 三個狀態:PENDING、FULFILLED、REJECTED
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    // 默認狀態爲 PENDING
    this.status = PENDING;
    // 存放成功狀態的值,默認爲 undefined
    this.value = undefined;
    // 存放失敗狀態的值,默認爲 undefined
    this.reason = undefined;

    // 調用此方法就是成功
    let resolve = (value) => {
      // 狀態爲 PENDING 時才能夠更新狀態,防止 executor 中調用了兩次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    } 

    // 調用此方法就是失敗
    let reject = (reason) => {
      // 狀態爲 PENDING 時才能夠更新狀態,防止 executor 中調用了兩次 resovle/reject 方法
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }

    try {
      // 當即執行,將 resolve 和 reject 函數傳給使用者 
      executor(resolve,reject)
    } catch (error) {
      // 發生異常時執行失敗邏輯
      reject(error)
    }
  }

  // 包含一個 then 方法,並接收兩個參數 onFulfilled、onRejected
  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }
  }
}
複製代碼

寫完代碼咱們能夠測試一下:

const promise = new Promise((resolve, reject) => {
  resolve('成功');
}).then(
  (data) => {
    console.log('success', data)
  },
  (err) => {
    console.log('faild', err)
  }
)
複製代碼

控制檯輸出:

"success 成功"
複製代碼

如今咱們已經實現了一個基礎版的 Promise,可是還不要高興的太早噢,這裏咱們只處理了同步操做的 promise。若是在 executor()中傳入一個異步操做的話呢,咱們試一下:

const promise = new Promise((resolve, reject) => {
  // 傳入一個異步操做
  setTimeout(() => {
    resolve('成功');
  },1000);
}).then(
  (data) => {
    console.log('success', data)
  },
  (err) => {
    console.log('faild', err)
  }
)
複製代碼

執行測試腳本後發現,promise 沒有任何返回。

由於 promise 調用 then 方法時,當前的 promise 並無成功,一直處於 pending 狀態。因此若是當調用 then 方法時,當前狀態是 pending,咱們須要先將成功和失敗的回調分別存放起來,在executor()的異步任務被執行時,觸發 resolve 或 reject,依次調用成功或失敗的回調。

結合這個思路,咱們優化一下代碼:

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    // 存放成功的回調
    this.onResolvedCallbacks = [];
    // 存放失敗的回調
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 依次將對應的函數執行
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 依次將對應的函數執行
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

    try {
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    if (this.status === FULFILLED) {
      onFulfilled(this.value)
    }

    if (this.status === REJECTED) {
      onRejected(this.reason)
    }

    if (this.status === PENDING) {
      // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行
      this.onResolvedCallbacks.push(() => {
        onFulfilled(this.value)
      });

      // 若是promise的狀態是 pending,須要將 onFulfilled 和 onRejected 函數存放起來,等待狀態肯定後,再依次將對應的函數執行
      this.onRejectedCallbacks.push(()=> {
        onRejected(this.reason);
      })
    }
  }
}
複製代碼

測試一下:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功');
  },1000);
}).then(
  (data) => {
    console.log('success', data)
  },
  (err) => {
    console.log('faild', err)
  }
)
複製代碼

控制檯等待 1s 後輸出:

"success 成功"
複製代碼

ok!大功告成,異步問題已經解決了!

熟悉設計模式的同窗,應該意識到了這實際上是一個發佈訂閱模式,這種收集依賴 -> 觸發通知 -> 取出依賴執行的方式,被普遍運用於發佈訂閱模式的實現。

then 的鏈式調用&值穿透特性

咱們都知道,promise 的優點在於能夠鏈式調用。在咱們使用 Promise 的時候,當 then 函數中 return 了一個值,不論是什麼值,咱們都能在下一個 then 中獲取到,這就是所謂的then 的鏈式調用。並且,當咱們不在 then 中放入參數,例:promise.then().then(),那麼其後面的 then 依舊能夠獲得以前 then 返回的值,這就是所謂的值的穿透。那具體如何實現呢?簡單思考一下,若是每次調用 then 的時候,咱們都從新建立一個 promise 對象,並把上一個 then 的返回結果傳給這個新的 promise 的 then 方法,不就能夠一直 then 下去了麼?那咱們來試着實現一下。這也是手寫 Promise 源碼的重中之重,因此,打起精神來,重頭戲來咯!

有了上面的想法,咱們再結合 Promise/A+ 規範梳理一下思路:

  1. then 的參數 onFulfilledonRejected 能夠缺省,若是 onFulfilled 或者 onRejected不是函數,將其忽略,且依舊能夠在下面的 then 中獲取到以前返回的值;「規範 Promise/A+ 2.2.一、2.2.1.一、2.2.1.2」
  2. promise 能夠 then 屢次,每次執行完 promise.then 方法後返回的都是一個「新的promise";「規範 Promise/A+ 2.2.7」
  3. 若是 then 的返回值 x 是一個普通值,那麼就會把這個結果做爲參數,傳遞給下一個 then 的成功的回調中;
  4. 若是 then 中拋出了異常,那麼就會把這個異常做爲參數,傳遞給下一個 then 的失敗的回調中;「規範 Promise/A+ 2.2.7.2」
  5. 若是 then 的返回值 x 是一個 promise,那麼會等這個 promise 執行完,promise 若是成功,就走下一個 then 的成功;若是失敗,就走下一個 then 的失敗;若是拋出異常,就走下一個 then 的失敗;「規範 Promise/A+ 2.2.7.三、2.2.7.4」
  6. 若是 then 的返回值 x 和 promise 是同一個引用對象,形成循環引用,則拋出異常,把異常傳遞給下一個 then 的失敗的回調中;「規範 Promise/A+ 2.3.1」
  7. 若是 then 的返回值 x 是一個 promise,且 x 同時調用 resolve 函數和 reject 函數,則第一次調用優先,其餘全部調用被忽略;「規範 Promise/A+ 2.3.3.3.3」

咱們將代碼補充完整:

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) => {
  // 本身等待本身完成是錯誤的實現,用一個類型錯誤,結束掉 promise Promise/A+ 2.3.1
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // Promise/A+ 2.3.3.3.3 只能調用一次
  let called;
  // 後續的條件要嚴格判斷 保證代碼能和別的庫一塊兒使用
  if ((typeof x === 'object' && x != null) || typeof x === 'function') { 
    try {
      // 爲了判斷 resolve 過的就不用再 reject 了(好比 reject 和 resolve 同時調用的時候) Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        // 不要寫成 x.then,直接 then.call 就能夠了 由於 x.then 會再次取值,Object.defineProperty Promise/A+ 2.3.3.3
        then.call(x, y => { // 根據 promise 的狀態決定是成功仍是失敗
          if (called) return;
          called = true;
          // 遞歸解析的過程(由於可能 promise 中還有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, r => {
          // 只要失敗就失敗 Promise/A+ 2.3.3.3.2
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 若是 x.then 是個普通值就直接返回 resolve 做爲結果 Promise/A+ 2.3.3.4
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 若是 x 是個普通值就直接返回 resolve 做爲結果 Promise/A+ 2.3.4 
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

    try {
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }

  then(onFulfilled, onRejected) {
    //解決 onFufilled,onRejected 沒有傳值的問題
    //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    //由於錯誤的值要讓後面訪問到,因此這裏也要跑出個錯誤,否則會在以後 then 的 resolve 中捕獲
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 每次調用 then 都返回一個新的 promise Promise/A+ 2.2.7
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //Promise/A+ 2.2.2
        //Promise/A+ 2.2.4 --- setTimeout
        setTimeout(() => {
          try {
            //Promise/A+ 2.2.7.1
            let x = onFulfilled(this.value);
            // x多是一個proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            //Promise/A+ 2.2.7.2
            reject(e)
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        //Promise/A+ 2.2.3
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e)
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(()=> {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          }, 0);
        });
      }
    });
  
    return promise2;
  }
}
複製代碼

測試一下:

const promise = new Promise((resolve, reject) => {
  reject('失敗');
}).then().then().then(data=>{
  console.log(data);
},err=>{
  console.log('err',err);
})

複製代碼

控制檯輸出:

"失敗 err"
複製代碼

至此,咱們已經完成了 promise 最關鍵的部分:then 的鏈式調用和值的穿透。搞清楚了 then 的鏈式調用和值的穿透,你也就搞清楚了 Promise。

測試 Promise 是否符合規範

Promise/A+規範提供了一個專門的測試腳本,能夠測試所編寫的代碼是否符合Promise/A+的規範。

首先,在 promise 實現的代碼中,增長如下代碼:

Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
      dfd.resolve = resolve;
      dfd.reject = reject;
  })
  return dfd;
}
複製代碼

安裝測試腳本:

npm install -g promises-aplus-tests
複製代碼

若是當前的 promise 源碼的文件名爲 promise.js

那麼在對應的目錄執行如下命令:

promises-aplus-tests promise.js
複製代碼

promises-aplus-tests 中共有 872 條測試用例。以上代碼,能夠完美經過全部用例。

Promise 的 API

雖然上述的 promise 源碼已經符合 Promise/A+ 的規範,可是原生的 Promise 還提供了一些其餘方法,如:

  • Promise.resolve()
  • Promise.reject()
  • Promise.prototype.catch()
  • Promise.prototype.finally()
  • Promise.all()
  • Promise.race()

下面具體說一下每一個方法的實現:

Promise.resolve

默認產生一個成功的 promise。

static resolve(data){
  return new Promise((resolve,reject)=>{
    resolve(data);
  })
}
複製代碼

這裏須要注意的是,promise.resolve 是具有等待功能的。若是參數是 promise 會等待這個 promise 解析完畢,在向下執行,因此這裏須要在 resolve 方法中作一個小小的處理:

let resolve = (value) => {
  // ======新增邏輯======
  // 若是 value 是一個promise,那咱們的庫中應該也要實現一個遞歸解析
  if(value instanceof Promise){
      // 遞歸解析 
      return value.then(resolve,reject)
  }
  // ===================
  if(this.status ===  PENDING) {
    this.status = FULFILLED;
    this.value = value;
    this.onResolvedCallbacks.forEach(fn=>fn());
  }
}
複製代碼

測試一下:

Promise.resolve(new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok');
  }, 3000);
})).then(data=>{
  console.log(data,'success')
}).catch(err=>{
  console.log(err,'error')
})
複製代碼

控制檯等待 3s 後輸出:

"ok success"
複製代碼

Promise.reject

默認產生一個失敗的 promise,Promise.reject 是直接將值變成錯誤結果。

static reject(reason){
  return new Promise((resolve,reject)=>{
    reject(reason);
  })
}
複製代碼

Promise.prototype.catch

Promise.prototype.catch 用來捕獲 promise 的異常,就至關於一個沒有成功的 then

Promise.prototype.catch = function(errCallback){
  return this.then(null,errCallback)
}
複製代碼

Promise.prototype.finally

finally 表示不是最終的意思,而是不管如何都會執行的意思。 若是返回一個 promise 會等待這個 promise 也執行完畢。若是返回的是成功的 promise,會採用上一次的結果;若是返回的是失敗的 promise,會用這個失敗的結果,傳到 catch 中。

Promise.prototype.finally = function(callback) {
  return this.then((value)=>{
    return Promise.resolve(callback()).then(()=>value)
  },(reason)=>{
    return Promise.resolve(callback()).then(()=>{throw reason})
  })  
}
複製代碼

測試一下:

Promise.resolve(456).finally(()=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
        resolve(123)
    }, 3000);
  })
}).then(data=>{
  console.log(data,'success')
}).catch(err=>{
  console.log(err,'error')
})
複製代碼

控制檯等待 3s 後輸出:

"456 success"
複製代碼

Promise.all

promise.all 是解決併發問題的,多個異步併發獲取最終的結果(若是有一個失敗則失敗)。

Promise.all = function(values) {
  if (!Array.isArray(values)) {
    const type = typeof values;
    return new TypeError(`TypeError: ${type} ${values} is not iterable`)
  }
  
  return new Promise((resolve, reject) => {
    let resultArr = [];
    let orderIndex = 0;
    const processResultByKey = (value, index) => {
      resultArr[index] = value;
      if (++orderIndex === values.length) {
          resolve(resultArr)
      }
    }
    for (let i = 0; i < values.length; i++) {
      let value = values[i];
      if (value && typeof value.then === 'function') {
        value.then((value) => {
          processResultByKey(value, i);
        }, reject);
      } else {
        processResultByKey(value, i);
      }
    }
  });
}
複製代碼

測試一下:

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('ok1');
  }, 1000);
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('ok2');
  }, 1000);
})

Promise.all([1,2,3,p1,p2]).then(data => {
  console.log('resolve', data);
}, err => {
  console.log('reject', err);
})
複製代碼

控制檯等待 1s 後輸出:

"resolve [ 1, 2, 3, 'ok1', 'ok2' ]"
複製代碼

Promise.race

Promise.race 用來處理多個請求,採用最快的(誰先完成用誰的)。

Promise.race = function(promises) {
  return new Promise((resolve, reject) => {
    // 一塊兒執行就是for循環
    for (let i = 0; i < promises.length; i++) {
      let val = promises[i];
      if (val && typeof val.then === 'function') {
        val.then(resolve, reject);
      } else { // 普通值
        resolve(val)
      }
    }
  });
}
複製代碼

特別須要注意的是:由於Promise 是沒有中斷方法的,xhr.abort()、ajax 有本身的中斷方法,axios 是基於 ajax 實現的;fetch 基於 promise,因此他的請求是沒法中斷的。

這也是 promise 存在的缺陷,咱們可使用 race 來本身封裝中斷方法:

function wrap(promise) {
  // 在這裏包裝一個 promise,能夠控制原來的promise是成功仍是失敗
  let abort;
  let newPromise = new Promise((resolve, reject) => { // defer 方法
      abort = reject;
  });
  let p = Promise.race([promise, newPromise]); // 任何一個先成功或者失敗 就能夠獲取到結果
  p.abort = abort;
  return p;
}

const promise = new Promise((resolve, reject) => {
  setTimeout(() => { // 模擬的接口調用 ajax 確定有超時設置
      resolve('成功');
  }, 1000);
});

let newPromise = wrap(promise);

setTimeout(() => {
  // 超過3秒 就算超時 應該讓 proimise 走到失敗態
  newPromise.abort('超時了');
}, 3000);

newPromise.then((data => {
    console.log('成功的結果' + data)
})).catch(e => {
    console.log('失敗的結果' + e)
})
複製代碼

控制檯等待 1s 後輸出:

"成功的結果成功"
複製代碼

promisify

promisify 是把一個 node 中的 api 轉換成 promise 的寫法。 在 node 版本 12.18 以上,已經支持了原生的 promisify 方法:const fs = require('fs').promises

const promisify = (fn) => { // 典型的高階函數 參數是函數 返回值是函數 
  return (...args)=>{
    return new Promise((resolve,reject)=>{
      fn(...args,function (err,data) { // node中的回調函數的參數 第一個永遠是error
        if(err) return reject(err);
        resolve(data);
      })
    });
  }
}
複製代碼

若是想要把 node 中全部的 api 都轉換成 promise 的寫法呢:

const promisifyAll = (target) =>{
  Reflect.ownKeys(target).forEach(key=>{
    if(typeof target[key] === 'function'){
      // 默認會將原有的方法 所有增長一個 Async 後綴 變成 promise 寫法
      target[key+'Async'] = promisify(target[key]);
    }
  });
  return target;
}
複製代碼

小結

寫了將近兩萬字,到這裏,咱們終於能夠給手寫 promise 作一個結尾了。咱們先是從 promise 的使用方法入手,構造出了 promise 的大體框架,而後根據 promise/A+ 規範填充代碼,重點實現了 then 的鏈式調用和值的穿透;而後使用測試腳本對所寫的代碼是否符合規範進行了測試;最後完成了 Promise 的 API 的實現。弄懂 promise 其實並不複雜,歸根結底仍是孰能生巧,沒事仍是要多加練習才行吖。

查看完整代碼請戳:github.com/qiruohan/ar…

因爲篇幅過長,因此這篇文章主要講了面試題的一、二、三、四、6,關於第五點我會在講 EventLoop 的文章中再進行系統的梳理,相信在你看過 Promise 的源碼以後,再理解 EventLoop,也會更加好理解了。

計劃輸出的相關內容文章:

  1. JS 的循環機制EventLoop(主線程、微任務、渲染、宏任務)。
  2. Generator/async+await 的實現。

若是你對個人文章感興趣,歡迎關注我噢!若是你對文章有任何的疑問,也歡迎給我留言~

參考

9k字 | Promise/async/Generator實現原理解析

Promise之你看得懂的Promise

Promise的源碼實現(完美符合Promise/A+規範)

結語

歡迎你們關注個人公衆號,一塊兒研究前端技術,期待與你共同進步。

相關文章
相關標籤/搜索