一步一步實現符合A+規範的promise

實現構造函數

const promise = new Promsie((resolve, reject) => {
    // ...
})
複製代碼

promise的狀態

promise有三種狀態,pending、fulfilled、rejected。而且狀態一旦由初始狀態(pending)變爲其餘狀態(fulfilled、rejected)便不能再次更改npm

promise的執行器

promise的執行器executor是構造函數的function類型的參數,executor接收兩個參數resolve, reject設計模式

resolve

resolve是promise內部實現的函數,接受一個參數,promise會將該參數存儲起來,將該參數做爲參數傳遞給then方法成功回調函數的參數,將promise的狀態由pending改變爲fulFilled;promise

rejected

reject一樣是promise內部實現的函數也接受一個參數,promise會將該參數存儲起來,將該參數做爲參數傳遞給then方法失敗回調函數的參數,將promise的狀態由pending改變爲rejected。框架

實現constructor的代碼異步

class Promise {
    constructor(executor) {
        this.state = 'pending'; // 初始化爲pending狀態
        this.value = undefined; // 存儲resolve接受的參數
        this.reason = undefined; // 存儲reject接受的參數
        const resolve = val => { // resovle的內部實現
            if (this.state === 'pending') { // 只有處於pending狀態才能更改狀態,防止二次更改狀態
                this.value = val; 
                this.state = 'fulfilled'; //更改狀態
            }
        }
        const reject = err => { // reject的內部實現
            if (this.state === 'pending') { // 只有處於pending狀態才能更改狀態,防止二次更改狀態
                this.reason = err;
                this.state = 'rejected'; // 更改狀態
            }
        }
        executor(resolve, reject); // 執行執行器
    }
}
複製代碼

執行器出錯

在開發中,若是executor中出現了錯誤,promise的處理:調用reject,並將錯誤做爲參數傳遞給reject函數

修改代碼測試

class Promise {
    constructor(executor) {
        ...
        try {
            executor(resolve, reject); // 執行執行器
        } catch(error) {
            reject(error);
        }
    }
}
複製代碼

實現then方法

then方法接受兩個函數參數,分別是成功狀態的回調函數和失敗狀態的回調函數。ui

class Promise {
    constructor(executor) {
        ...
    }
    then(onFulfilled, onRejected) {
        if (this.state === 'fulfilled') { // 成功時調用成功狀態的回調
            onFulfilled(this.value);
        }
        if (this.state === 'rejected') { // 失敗時調用失敗狀態的回調
            onRejected(this.reason);
        }
    }
}
複製代碼

如今能夠看到,promise的三種狀態是會頻繁用到的,若是每次判斷狀態都用手寫的字符串可能會有將狀態字符創寫錯的狀況。爲了不這種錯誤,咱們用三個常量表示這三種狀態。this

重構後的代碼以下spa

/* 增長的代碼 */
const PENDING = 'PENDING'; // 初始狀態
const FULFILLED = 'FULFILLED'; // 成功狀態
const REJECTED = 'REJECTED'; // 失敗狀態
/* 增長的代碼 */
class Promise {
    constructor(executor) {
        this.state = PENDING; // 初始化爲pending狀態
        this.value = undefined; // 存儲resolve接受的參數
        this.reason = undefined; // 存儲reject接受的參數
        const resolve = val => { // resovle的內部實現
            if (this.state === PENDING) { // 只有處於pending狀態才能更改狀態,防止二次更改狀態
                this.value = val; 
                this.state = FULFILLED; //更改狀態
            }
        }
        const reject = err => { // reject的內部實現
            if (this.state === PENDING) { // 只有處於pending狀態才能更改狀態,防止二次更改狀態
                this.reason = err;
                this.state = REJECTED; // 更改狀態
            }
        }
        try {
            executor(resolve, reject); // 執行執行器
        } catch(error) {
            reject(error);
        }
    }
    then(onFulfilled, onRejected) {
        if (this.state === FULFILLED) { // 成功時調用成功狀態的回調
            onFulfilled(this.value);
        }
        if (this.state === REJECTED) { // 失敗時調用失敗狀態的回調
            onRejected(this.reason);
        }
    }
}
複製代碼

同一個promise的then方法能夠調用屢次

promise A+規範:同一個promise的then方法能夠調用(非鏈式調用)屢次且每次調用的結果相同

let p = new Promise(resolve => {
    resolve(1);
})
p.then(data => {
    console.log(data) // 輸出1
})
p.then(data => {
    console.log(data) // 輸出1
})
複製代碼

因爲咱們在調用resolve或者reject時已經將參數存儲在promise內部,因此咱們的代碼符合這條規則

resolve或reject的調用處於異步代碼塊中

如今有一個問題,用過promise的小夥伴都知道,在new一個Promise的時候,若是resolve或者reject的調用處於異步代碼塊中,then方法也是能夠在resolve以後正常執行的。而如今咱們的代碼明顯是沒法處理resolve或者reject的調用處於異步代碼塊中的狀況。由於若是resolve或者reject的調用處於異步代碼塊中,當調用then方法是promise還處於pending狀態。

那如何解決這個問題呢?咱們能夠想到:若是promise處於pending狀態時,能夠先將then方法接收的回調函數先存儲起來,等promise的狀態由pending變爲fulfilled或者rejected時再調用相應地回調函數。看到這裏有沒有想起一種很經常使用的設計模式?這種模式的描述:它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。沒錯,就是觀察者模式或者說是發佈訂閱模式。

再來修改咱們的代碼

首先是修改構造函數

constructor(executor) {
    this.state = PENDING; 
    this.value = undefined;
    this.reason = undefined;
    /* 增長的代碼 */
    this.onFulfilledCallbacks = []; // 存儲成功狀態的回調函數
    this.onRejectedCallbacks = []; // 存儲失敗狀態的回調函數
    /* 增長的代碼 */
    const resolve = val => { 
        if (this.state === PENDING) { 
            this.value = val; 
            this.state = FULFILLED;
            /* 增長的代碼 */
            this.onFulfilledCallbacks.forEach(fn => fn(this.value)) // 狀態改變,通知回調函數執行
            /* 增長的代碼 */
        }
    }
    const reject = err => { 
        if (this.state === PENDING) { 
            this.reason = err;
            this.state = REJECTED;
            /* 增長的代碼 */
            this.onRejectedCallbacks.forEach(fn => fn(this.reason)) // 狀態改變,通知回調函數執行
            /* 增長的代碼 */
        }
    }
    executor(resolve, reject); // 執行執行器
}
複製代碼

修改then方法

then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) { 
        onFulfilled(this.value);
    }
    if (this.state === REJECTED) { 
        onRejected(this.reason);
    }
    /* 增長的代碼 */
    if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled); // 存儲回調函數
        this.onRejectedCallbacks.push(onRejected); // 存儲回調函數
    }
    /* 增長的代碼 */
}
複製代碼

如今咱們來測試一下

let promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
})
promise.then(data => {
  console.log(data) 
})
// 結果:打印1
複製代碼

resolve的參數是一個promise實例

若是resolve的參數是一個promise實例,那麼須要取得其最終(可能嵌套多層promise)的值

修改後的constructor

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

    let resolve = (value) => { // 將promise的狀態改成fulfilled狀態
      if (value instanceof Promise) { // 遞歸解析resolve傳入的值是promise的狀況
        return value.then(resolve, reject);
      }
      if (this.status === PENDING) { // 保證狀態一旦改變便不能再次改變
        this.value = value;
        this.status = FULFILLED;
        this.onFulfilledCallbacks.forEach(onFulfilled => {
          onFulfilled();
        })
      }
    }
    let reject = (reason) => { // 將promise的狀態改成rejected狀態
      if (this.status === PENDING) { // 保證狀態一旦改變便不能再次改變
        this.reason = reason;
        this.status = REJECTED;
        this.onRejectedCallbacks.forEach(onRejected => {
          onRejected();
        })
      }
    }
    try { // 執行器函數有可能會有異常,若是出現異常,執行reject
      executor(resolve, reject)
    } catch (e) {
      reject(e);
    }
}
複製代碼

到了這裏小夥伴可能會想到,reject是否也要作一樣的處理?並不須要。promiseA+規範規定reject直接將傳入的失敗的緣由傳出便可。這也是很是符合邏輯的,由於一旦reject,就代表出現了錯誤,並不須要得到數據。而resolve的職責是將數據傳出,因此才須要遞歸解析來得到其最終的值,而不是一個promise實例。

傳入的onFulfilled或者onRejected不是函數

若是onFulfilled或者onRejected不是函數,那麼咱們須要將其從新賦值

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; 
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    if (this.state === FULFILLED) { 
        onFulfilled(this.value);
    }
    if (this.state === REJECTED) { 
        onRejected(this.reason);
    }
    /* 增長的代碼 */
    if (this.state === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled); // 存儲回調函數
        this.onRejectedCallbacks.push(onRejected); // 存儲回調函數
    }
    /* 增長的代碼 */
}
複製代碼

實現then方法的鏈式調用

在實現鏈式調用以前,須要知道的then的詳細用法。then的鏈式調用中

  1. 前一個then的回調函數的返回值會傳遞給下一個then的回調函數的參數;
  2. 若是前一個then方法的回調函數在執行的過程當中發生錯誤,將此錯誤做爲參數傳遞給下一個then方法,下一個then方法執行失敗的回調。
  3. 若是前一個then的返回值是一個promise,
    1. 若是返回的promise是fulfilled狀態,則下一個then方法執行成功的回調,
    2. 若是返回的promise是rejected狀態,則下一個then方法執行失敗的回調,
    3. 若是返回的promise是pending狀態,那麼下面的then方法都不會執行;

咱們先不考慮then方法的回調函數的返回值是promise的狀況,先實現1,2

既然可以鏈式調用,那麼then方法的返回值一定是一個Promise實例。那麼返回的promise實例是否是自身呢?答案顯而易見:不是。若是一個promise的then方法的返回值是promise自身,在new一個Promise時,調用了resolve方法,由於promise的狀態一旦更改便不能再次更改,那麼下面的全部then便只能執行成功的回調,沒法進行錯誤處理,這顯然並不符合promise的規範和設計promise的初衷。

而實際的then方法的鏈式調用規則是這樣的:若是promise的前一個then方法執行的是成功(或者失敗)的回調,那麼下一個then方法有可能執行成功的回調,也有可能執行失敗的回調,顯然then返回的是一個新的promise實例。

修改then方法

then(onFulfilled, onRejected) {
    const promise2 = new Promise((resolve, reject) => { // 待返回的新的promise實例
        if (this.state === FULFILLED) {
            try {
                let x = onFulfilled(this.value); 
                resolve(x); // 取得promise的then方法回調函數的返回值,將其存儲到promise2中
            } catch(error) {
                reject(error) // 若是出錯這次的then方法的回調函數出錯,在將錯誤傳遞給promise2
            }
        }
        if (this.state === REJECTED) {
            try {
                let x = onRejected(this.reason);
                resolve(x);
            } catch (error) {
                reject(error);
            }
        }
        if (this.state === PENDING) {
            this.onFulfilledCallbacks.push(() => {
                try {
                    let x = onFulfilled(this.value);
                    resolve(x);
                }
            }); 
            this.onRejectedCallbacks.push(() => {
                try {
                    let x = onRejected(this.reason);
                    resolve(x);
                } catch (error) {
                    reject(error);
                }
            }); 
        }
    })
    return promise2;
}
複製代碼

處理then方法的回調函數的返回值是promise實例的狀況

因爲有多處須要處理回調函數的返回值,因此須要將處理的過程封裝成一個方法。咱們把這個方法命名爲resolvePromise。接下來咱們思考resolvePromise這個方法須要哪些參數呢?x(回調函數的返回值)確定是必須的,在方法體內還須要更改promise2的狀態,因此須要將resovle和reject也傳遞進去。還有一個必須的參數,那就是promise2自己。

爲何須要將promise2也傳遞進去?

由於要防止一種邏輯上的錯誤:就是x與promise2引用的是同一個對象。

let p = new Promise(resolve => {
    resolve(1);
});
let p1 = p.then(data => {
    return p1;
})
複製代碼

若是你測試上述代碼,則會看到以下報錯信息

UnhandledPromiseRejectionWarning: TypeError: Chaining cycle detected for promise #<Promise> 複製代碼

這在邏輯上上行不通的,若是promise2 === x,那麼就至關於你本身在等着你本身幹完某件事情,而你不作任何事情僅僅是在等着,這是一個悖論,你要完成的事情永遠也沒法完成。在代碼實現上就是判斷promise2和x是否引用了同一個對象,若是相同則拋出一個TypeError(這是promiseA+規範規定的)

接下來就是處理x是promise仍是普通值。

那麼如何斷定某個變量是一個promise實例呢?分爲3步:

  1. 判斷該變量是不是對象或者是函數類型
  2. 若是知足1,判斷該變量是否有then屬性
  3. 若是知足2,判斷then是不是一個函數

若是三條都知足,則認爲這是一個promise的實例,不然就是一個普通值

一句話總結如何判斷某個變量是不是promise的實例:該變量是不是一個擁有then方法的對象或者函數。雖然這樣判斷並不必定準確(甚至對某些js庫產生了影響),可是promiseA+規範是如此規定的。

接下的須要作的事情就很清晰了。x若是不是promise的實例,直接調用傳遞進來的resolve將x傳出;若是x是promise的實例,那麼就調用x的then方法,將x(注意此時的x是一個promise實例)存儲的成功的值或者失敗的緣由取出,再對取出的值進行一次解析,直到取出的值爲普通值。

在處理x是promise的狀況中,有幾點須要注意的地方:

  1. 處理過程當中可能會出錯,若是出錯就調用reject將錯誤傳出
  2. 有可能在應用中你採用的promise和第三方框架中的promise不一樣,第三方框架中的promsie不必定徹底知足promiseA+規範,若是不作處理,有可能出現狀態二次改變的狀況。因此要防止promise狀態的二次改變,咱們須要一個標誌,表示x的狀態是否已經改變,若已經改變則不往下進行,直接返回。
  3. resolvePromise中須要傳入promise2,而resolvePromise是在promise2生成的過程當中調用的,也就是說promise2尚未生成,就要對其進行訪問,這確定是行不通的。因此咱們能夠將resolvePromise放到異步代碼塊中來解決這個問題。

修改後的then方法

then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val; 
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err)
          }
        })
      }
      if (this.status === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        })
      }
      // 當promise的執行器中有異步代碼的時候,而且觸發狀態改變的resolve或者reject
      // 在異步代碼塊中。這種狀況下,因爲先執行了then,而promise的狀態仍是PENDING狀態
      // 因此then方法內的函數便沒法執行。
      // 此時可先將then方法內的函數參數存儲起來,利用訂閱-發佈模式,當執行器中等的異步代碼
      // 執行後,觸發存儲起來的函數的執行。
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err)
            }
          })
        })
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          })
        })
      }
    })
    return promise2;
}
複製代碼

resolvePromise的實現代碼

const resolvePromise = function (promise2, x, resolve, reject) {
  if (x === promise2) { // x和promise引用同一個對象的狀況
    throw new TypeError('Chaining cycle detected for promise #<Promise>');
  }
  let isCalled = false; // 判斷是否已經resolve或者reject,防止狀態二次改變
  // 判斷x是不是一個promise:
  // 1.判斷是不是對象或者function
  // 2.判斷是否有then,而且then是一個方法
  if ((x !== null && typeof x === 'object') || typeof x === 'function') {
    try { // 考慮一種很極端的狀況,即then屬性的getter方法內拋出異常,就是一取x.then就報錯
      let then = x.then;
      if (typeof then === 'function') {
        then.call(x, (y) => { // 之因此不直接再次x.then,也是考慮到一個極端的狀況,就是傳入的x的then方法第一次訪問不報錯,當第二次訪問的時候就報錯(then是一種高階函數)
          if (isCalled) return;
          isCalled = true;
          resolvePromise(promise2, y, resolve, reject); // 遞歸解析
        }, (r) => {
          if (isCalled) return; // 若是已經調用過了resolve
          isCalled = true;
          reject(r);
        }) // 考慮到高階函數當執行第二次後可能有不一樣的行爲
      } else { // then屬性不是函數
        resolve(x); // 將值傳出
      }
    } catch (err) {
      if (isCalled) return; // 若是已經調用過了resolve
      isCalled = true;
      reject(err); // 將錯誤傳出
    }
  } else {
    resolve(x); // 將值傳出
  }
}
複製代碼

promise完整實現

測試promise是否符合規範:

第一步,經過yarn或者npm全局安裝promises-aplus-tests包

第二步,在實現promise的文件中加入以下代碼

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

第三步,運行命令promises-aplus-tests 實現promise的js文件路徑

完整實現的代碼

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

class Promise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.fulfilledCallbacks = []; // 存儲
    this.rejectedCallbacks = [];
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      if (this.state === PENDING) {
        this.value = value;
        this.state = FULFILLED;
        this.fulfilledCallbacks.forEach(fn => fn());
      }
    }
    const reject = (reason) => {
      if (this.state === PENDING) {
        this.reason = reason;
        this.state = REJECTED;
        this.rejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      executor(resolve, reject);
    } catch(error) {
      reject(error);
    };
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
    let promise2 = new Promise((resolve, reject) => {
      if (this.state === FULFILLED) {
        setTimeout(() => {
          try {
            // 因爲onFulfilled 或 onRejected的返回值多是promise
            // 故須要一個特殊的方法來處理各類狀況
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch(error) {
            reject(error);
          }
        })
      }
      if (this.state === REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        })
      }
      if (this.state === PENDING) {
        this.fulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch(error) {
              reject(error)
            }
          })
        });
        this.rejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch(error) {
              reject(error);
            }
          })
        });
      }
    })
    return promise2;
  }
}
// 因爲onFulfilled 或 onRejected的返回值多是promise
// 故須要一個特殊的方法來處理各類狀況
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  let isCalled = false;
  if (typeof x === 'object' && x !== null || typeof x === 'function') {
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(x, y => {
          if (isCalled) {
            return;
          }
          isCalled = true;
          resolvePromise(promise2, y, resolve, reject);
        }, r => {
          if (isCalled) {
            return;
          }
          isCalled = true;
          reject(r);
        })
      } else {
        resolve(x);
      }
    } catch (error) {
      if (isCalled) {
        return;
      }
      isCalled = true;
      reject(error);
    }
  } else {
    resolve(x);
  }
}
Promise.defer = Promise.deferred = function(){
  let dfd = {};
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
複製代碼
相關文章
相關標籤/搜索