詳細的Promise源碼實現,再被面試問到輕鬆解答

寫這篇文章的目的是解剖Promise源碼,原由也是最近秋招被問到了讓手寫Promise,另外在網上看到的Promise源碼或多或少有些小問題,也就是沒有徹底遵循Promise/A+規範。javascript

代碼會徹底使用ES6語法,主要分如下幾個模塊:java

  • 總體分析(爲代碼書寫鋪路)
  • 實現第一版(構造函數大體功能億完善)
  • 支持異步和鏈式調用(完善then方法)
  • 實現catch方法
  • 實現Promise.resolve()
  • 實現Promise.reject()
  • 實現Promise.all()
  • 實現Promise.race()

1、總體分析

所謂Promise就是一個容器,有三個狀態:PENDING(進行中)、FULFILLED(成功)、REJECTED(失敗),裏面保存着某個將來纔會結束的事件(一般是一個異步操做)的結果,有兩大特色:數組

  • 容器狀態不受外界影響
  • 一旦狀態改變就不會再變,任什麼時候候均可以獲得這個結果

來看下Promise的用法:promise

new Promise((resolve, reject) => {
  // ...
  // 成功則執行resolve,不然指定reject
}).then(
  res => {
    // resolve對應觸發函數的執行
  },
  err => {
    // reject對應觸發函數的執行
  }
).then(
  // 支持鏈式調用
  res => {
  
  }
).catch(
  err => console.log(err)
)
Promise.resolve();
Promise.reject();
Promise.all([promise1, promise2, ...]).then();
Promise.race([promise1, promise2, ...]).then();
複製代碼

經過用法不難分析出:markdown

  • Promise構造函數接受一個函數參數exector,exector接受resolvereject兩個函數並當即執行,經過resolve/reject改變狀態
  • 狀態改變後,觸發原型鏈上的then、catch方法
  • Promise類擁有靜態方法resolve、reject、all、race

那麼能夠寫出大體結構代碼:dom

class Promise {
  constructor(exector) {
    const resolve = () => {

    }
    const reject = () => {

    }
    exector(resolve, reject);
  }
  then() {

  }
  catch() {

  }
  static resolve() {

  }
  static reject() {

  }
  static all() {

  }
  static race() {
    
  }
}
複製代碼

以後在此基礎上補充代碼。異步

2、實現第一版

首先引入三種狀態,完善resolvereject函數,最後在構造函數內執行exector(resolve, reject)函數

// 定義三種狀態
const PENDING = 'PENDING';      // 進行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失敗

class Promise {
  constructor(exector) {
    // 初始化狀態
    this.status = PENDING;
    // 將成功、失敗結果放在this上,便於then、catch訪問
    this.value = undefined;
    this.reason = undefined;

    const resolve = value => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
      }
    }
    const reject = reason => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
      }
    }
    // 當即執行exector
    // 把內部的resolve和reject傳入executor,用戶可調用resolve和reject
    exector(resolve, reject);
  }
}
複製代碼

注意:exector(resolve, reject);執行可能會報錯,因此須要使用try包括一下,有報錯reject拋出去。測試

constructor(exector) {
  // 初始化狀態
  this.status = PENDING;
  // 將成功、失敗結果放在this上,便於then、catch訪問
  this.value = undefined;
  this.reason = undefined;

  const resolve = value => {
    if (this.status === PENDING) {
      // 只有進行中狀態才能更改狀態
      this.status = FULFILLED;
      this.value = value;
    }
  }
  const reject = reason => {
    if (this.status === PENDING) {
      // 只有進行中狀態才能更改狀態
      this.status = REJECTED;
      this.reason = reason;
    }
  }
  // 修改代碼
  try {
    // 當即執行executor
    // 把內部的resolve和reject傳入executor,用戶可調用resolve和reject
    exector(resolve, reject);
  } catch(e) {
    // executor執行出錯,將錯誤內容reject拋出去
    reject(e);
  }
}
複製代碼

此時可使用then進行捕獲了,then接收兩個函數,分別對應FULFILLEDREJECTED狀態:ui

new Promise().then(
  res => {},
  err => {},
)
複製代碼

注意:thencatch是微任務,這裏使用setTimeout模擬:

then(onFulfilled, onRejected) {
  // then是微任務,這裏用setTimeout模擬
  setTimeout(() => {
    if (this.status === FULFILLED) {
      // FULFILLED狀態下才執行
      onFulfilled(this.value);
    } else if (this.status === REJECTED) {
      // REJECTED狀態下才執行
      onRejected(this.reason);
    }
  })
}
複製代碼

OK,第一版已經完成:

// 定義三種狀態
const PENDING = 'PENDING';      // 進行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失敗

class Promise {
  constructor(exector) {
    // 初始化狀態
    this.status = PENDING;
    // 將成功、失敗結果放在this上,便於then、catch訪問
    this.value = undefined;
    this.reason = undefined;

    const resolve = value => {
      if (this.status === PENDING) {
        // 只有進行中狀態才能更改狀態
        this.status = FULFILLED;
        this.value = value;
      }
    }
    const reject = reason => {
      if (this.status === PENDING) {
        // 只有進行中狀態才能更改狀態
        this.status = REJECTED;
        this.reason = reason;
      }
    }
    try {
      // 當即執行executor
      // 把內部的resolve和reject傳入executor,用戶可調用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor執行出錯,將錯誤內容reject拋出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    // then是微任務,這裏用setTimeout模擬
    setTimeout(() => {
      if (this.status === FULFILLED) {
        // FULFILLED狀態下才執行
        onFulfilled(this.value);
      } else if (this.status === REJECTED) {
        // REJECTED狀態下才執行
        onRejected(this.reason);
      }
    })
  }
}
複製代碼

能夠拿數據測試一下:

const promise = new Promise((resolve, reject) => {
  Math.random() < 0.5 ? resolve(1) : reject(-1);
}).then(
  res => console.log(res),
  err => console.log(err),
)
複製代碼

3、支持異步和鏈式調用

此時第一版還有三個方向須要完善:

  1. Promise內部異步代碼執行的問題。
  2. Promise的鏈式調用
  3. 值傳透

支持異步代碼

開發中常常會將接口放於promise內部,等接口請求響應成功把數據resolve出去,或失敗時把數據reject出去,此時thencatch纔會進行捕獲。

而如今的代碼,promise內部若是有異步代碼執行後才resolvethen不會等待異步代碼執行完畢會直接執行,因此此時狀態是PENDING,不會觸發then的回調函數。

新增onFulfilledCallbacksonRejectedCallbacks維護成功態、失敗態任務隊列:

// 定義三種狀態
const PENDING = 'PENDING';      // 進行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失敗

class Promise {
  constructor(exector) {
    // 初始化狀態
    this.status = PENDING;
    // 將成功、失敗結果放在this上,便於then、catch訪問
    this.value = undefined;
    this.reason = undefined;
    
    // 新增代碼:
    // 成功態回調函數隊列
    this.onFulfilledCallbacks = [];
    // 失敗態回調函數隊列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 新增代碼:
        // 成功態函數依次執行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 新增代碼:
        // 失敗態函數依次執行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 當即執行executor
      // 把內部的resolve和reject傳入executor,用戶可調用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor執行出錯,將錯誤內容reject拋出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    // then是微任務,這裏用setTimeout模擬
    setTimeout(() => {
      // 新增代碼:
      if (this.status === PENDING) {
        // 狀態是PENDING下執行
        // 說明promise內部有異步代碼執行,還未改變狀態,添加到成功/失敗回調任務隊列便可
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
      }else if (this.status === FULFILLED) {
        // FULFILLED狀態下才執行
        onFulfilled(this.value);
      } else if (this.status === REJECTED) {
        // REJECTED狀態下才執行
        onRejected(this.reason);
      }
    })
  }
}

const promise = new Promise((resolve, reject) => {
  setTimeout(() => resolve(1), 1000);
}).then(
  res => console.log(res)
)
// 1
複製代碼

實現鏈式調用

Promise的一大優點就是支持鏈式調用,具體來講就是then方法的具體實現,其實是返回了一個Promise,須要注意的幾個點:

  1. 保存以前promise實例的引用,即保存this
  2. 根據then回調函數執行的返回值
  • 若是是promise實例,那麼返回的下一個promise實例會等待這個promise狀態發生變化
  • 若是不是promise實例,根據目前狀況直接執行resolvereject

完善then函數:

then(onFulfilled, onRejected) {
  // 保存this
  const self = this;
  return new Promise((resolve, reject) => {
    if (self.status === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        // try捕獲錯誤
        try {
          // 模擬微任務
          setTimeout(() => {
            const result = onFulfilled(self.value);
            // 分兩種狀況:
            // 1. 回調函數返回值是Promise,執行then操做
            // 2. 若是不是Promise,調用新Promise的resolve函數
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      });
      self.onRejectedCallbacks.push(() => {
        // 如下同理
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            // 不一樣點:此時是reject
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      })
    } else if (self.status === FULFILLED) {
      setTimeout(() => {
        try {
          const result = onFulfilled(self.value);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        } catch(e) {
          reject(e);
        }
      });
    } else if (self.status === REJECT){
      setTimeout(() => {
        try {
          const result = onRejected(self.error);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        } catch(e) {
          reject(e);
        }
      })
    }
  })
}
複製代碼

值傳透

Promise支持值穿透:

let promsie = new Promise((resolve,reject)=>{
  resolve(1)
})
.then(2)
.then(3)
.then(value => {
  console.log(value)
})
// 1
複製代碼

then參數指望是函數,傳入非函數則會發生值穿透。值傳透能夠理解爲,當傳入then的不是函數的時候,這個then是無效的。

原理上是當then中傳入的不算函數,則這個promise返回上一個promise的值,這就是發生值穿透的緣由,因此只須要對then的兩個參數進行設置就好了:

onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function'? onRejected:
    reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
複製代碼

完整的then函數代碼:

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  onRejected = typeof onRejected === 'function'? onRejected:
    reason => { throw new Error(reason instanceof Error ? reason.message:reason) }
  // 保存this
  const self = this;
  return new Promise((resolve, reject) => {
    if (self.status === PENDING) {
      self.onFulfilledCallbacks.push(() => {
        // try捕獲錯誤
        try {
          // 模擬微任務
          setTimeout(() => {
            const result = onFulfilled(self.value);
            // 分兩種狀況:
            // 1. 回調函數返回值是Promise,執行then操做
            // 2. 若是不是Promise,調用新Promise的resolve函數
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      });
      self.onRejectedCallbacks.push(() => {
        // 如下同理
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            // 不一樣點:此時是reject
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      })
    } else if (self.status === FULFILLED) {
      try {
        setTimeout(() => {
          const result = onFulfilled(self.value);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        });
      } catch(e) {
        reject(e);
      }
    } else if (self.status === REJECTED){
      try {
        setTimeout(() => {
          const result = onRejected(self.reason);
          result instanceof Promise ? result.then(resolve, reject) : resolve(result);
        })
      } catch(e) {
        reject(e);
      }
    }
  });
}
複製代碼

4、實現catch()方法

Promise.prototype.catch就是Promise.prototype.then(null, onRejected)的別名,因此實現就很簡單了:

catch(onRejected) {
  return this.then(null, onRejected);
}
複製代碼

5、Promise.resolve()

這裏就不考慮參數是thenable對象了,那麼參數有兩種狀況:

  1. Promise實例
  2. 不是Promise實例
static resolve(value) {
  if (value instanceof Promise) {
    // 若是是Promise實例,直接返回
    return value;
  } else {
    // 若是不是Promise實例,返回一個新的Promise對象,狀態爲FULFILLED
    return new Promise((resolve, reject) => resolve(value));
  }
}
複製代碼

6、Promise.reject()

Promise.reject也會返回一個Promise實例,狀態爲REJECTED

Promise.resolve不一樣的是,Promise.reject方法的參數會原封不動地做爲reject的參數

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

7、Promise.all()

返回一個promise對象,只有當全部promise都成功時返回的promise狀態才成功,須要注意的點是:

  1. 全部的promise狀態變爲FULFILLED,返回的promise狀態才變爲FULFILLED
  2. 一個promise狀態變爲REJECTED,返回的promise狀態就變爲REJECTED
  3. 數組成員不必定都是promise,須要使用Promise.resolve()處理。
static all(promiseArr) {
  const len = promiseArr.length;
  const values = new Array(len);
  // 記錄已經成功執行的promise個數
  let count = 0;
  return new Promise((resolve, reject) => {
    for (let i = 0; i < len; i++) {
      // Promise.resolve()處理,確保每個都是promise實例
      Promise.resolve(promiseArr[i]).then(
        val => {
          values[i] = val;
          count++;
          // 若是所有執行完,返回promise的狀態就能夠改變了
          if (count === len) resolve(values);
        },
        err => reject(err),
      );
    }
  })
}
複製代碼

8、Promise.race()

Promise.race()實現就比較簡單了:

static race(promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      Promise.resolve(p).then(
        val => resolve(val),
        err => reject(err),
      )
    })
  })
}
複製代碼

9、完整代碼

// 模擬實現Promise
// Promise利用三大手段解決回調地獄:
// 1. 回調函數延遲綁定
// 2. 返回值穿透
// 3. 錯誤冒泡

// 定義三種狀態
const PENDING = 'PENDING';      // 進行中
const FULFILLED = 'FULFILLED';  // 已成功
const REJECTED = 'REJECTED';    // 已失敗

class Promise {
  constructor(exector) {
    // 初始化狀態
    this.status = PENDING;
    // 將成功、失敗結果放在this上,便於then、catch訪問
    this.value = undefined;
    this.reason = undefined;
    // 成功態回調函數隊列
    this.onFulfilledCallbacks = [];
    // 失敗態回調函數隊列
    this.onRejectedCallbacks = [];

    const resolve = value => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        // 成功態函數依次執行
        this.onFulfilledCallbacks.forEach(fn => fn(this.value));
      }
    }
    const reject = reason => {
      // 只有進行中狀態才能更改狀態
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        // 失敗態函數依次執行
        this.onRejectedCallbacks.forEach(fn => fn(this.reason))
      }
    }
    try {
      // 當即執行executor
      // 把內部的resolve和reject傳入executor,用戶可調用resolve和reject
      exector(resolve, reject);
    } catch(e) {
      // executor執行出錯,將錯誤內容reject拋出去
      reject(e);
    }
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function'? onRejected :
      reason => { throw new Error(reason instanceof Error ? reason.message : reason) }
    // 保存this
    const self = this;
    return new Promise((resolve, reject) => {
      if (self.status === PENDING) {
        self.onFulfilledCallbacks.push(() => {
          // try捕獲錯誤
          try {
            // 模擬微任務
            setTimeout(() => {
              const result = onFulfilled(self.value);
              // 分兩種狀況:
              // 1. 回調函數返回值是Promise,執行then操做
              // 2. 若是不是Promise,調用新Promise的resolve函數
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        });
        self.onRejectedCallbacks.push(() => {
          // 如下同理
          try {
            setTimeout(() => {
              const result = onRejected(self.reason);
              // 不一樣點:此時是reject
              result instanceof Promise ? result.then(resolve, reject) : resolve(result);
            })
          } catch(e) {
            reject(e);
          }
        })
      } else if (self.status === FULFILLED) {
        try {
          setTimeout(() => {
            const result = onFulfilled(self.value);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          });
        } catch(e) {
          reject(e);
        }
      } else if (self.status === REJECTED) {
        try {
          setTimeout(() => {
            const result = onRejected(self.reason);
            result instanceof Promise ? result.then(resolve, reject) : resolve(result);
          })
        } catch(e) {
          reject(e);
        }
      }
    });
  }
  catch(onRejected) {
    return this.then(null, onRejected);
  }
  static resolve(value) {
    if (value instanceof Promise) {
      // 若是是Promise實例,直接返回
      return value;
    } else {
      // 若是不是Promise實例,返回一個新的Promise對象,狀態爲FULFILLED
      return new Promise((resolve, reject) => resolve(value));
    }
  }
  static reject(reason) {
    return new Promise((resolve, reject) => {
      reject(reason);
    })
  }
  static all(promiseArr) {
    const len = promiseArr.length;
    const values = new Array(len);
    // 記錄已經成功執行的promise個數
    let count = 0;
    return new Promise((resolve, reject) => {
      for (let i = 0; i < len; i++) {
        // Promise.resolve()處理,確保每個都是promise實例
        Promise.resolve(promiseArr[i]).then(
          val => {
            values[i] = val;
            count++;
            // 若是所有執行完,返回promise的狀態就能夠改變了
            if (count === len) resolve(values);
          },
          err => reject(err),
        );
      }
    })
  }
  static race(promiseArr) {
    return new Promise((resolve, reject) => {
      promiseArr.forEach(p => {
        Promise.resolve(p).then(
          val => resolve(val),
          err => reject(err),
        )
      })
    })
  }
}
複製代碼
相關文章
相關標籤/搜索