JavaScript中的promise(二)

簡介

上一節, 咱們介紹了js中promise的靜態方法和實例方法, 以及使用. 本節將手寫一個符合promiseA+規範的promise。javascript

  • 手寫一個Promise
  • 使用promises-aplus-tests來檢測是否符合promiseA+規範

先搭建一個Promise的架子.

新建一個xpromise.js的文件, 建一個class,取名爲XPromise. 把靜態方法和實例方法的簽名加上.java

代碼以下:git

class XPromise {
  constructor(cb) {
  }

  then(resolve, reject) {
  }

  catch(reject) {
  }

  finally(cb) {
  }
  
  static resolve(val) {
  }

  static reject(reason) {
  }

  static all(promises) {
  }

  static race(promises) {
  }

  static allSettled(promises) {
  }

  static deferred() {
  }
}
複製代碼

接下來, 咱們實現每個方法.github

實現構造函數.

描述: 咱們先思考一下, Promise構造器裏應該要作什麼.npm

  • 使用Promise時, 咱們要傳入一個回調, 回調裏提供兩個參數. resolve和reject. 如:
new Promise((resolve, reject) => {...})
複製代碼
  • Promise是有狀態的. pending, fulfilled和rejected三種
  • 保存了成功回調和失敗回調的返回值.
  • then能夠鏈式使用,成功回調和時候的回調應該是一個數組.

咱們就來實現以上功能, 代碼以下:數組

// 定義promise的狀態, 方便後面使用.
const s = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected'
};

constructor(cb) {
    // 設置初始狀態爲pending.
    this.status = s.pending;
    
    // 存儲成功後的值.
    this.value = null;
    
    // 存儲失敗的緣由.
    this.reason = null;
    
    // 執行成功的回調蒐集.
    this.resolveCallbacks = [];
    
    // 執行失敗的回調蒐集
    this.rejectCallbacks = [];
    
    const resolve = val => {
      // 若是狀態是pending, 就更改.
      if (this.status === s.pending) {
        this.status = s.fulfilled;
        this.value = val;
        this.resolveCallbacks.forEach(cb => cb())
      }
    };
    
    const reject = reason => {
      if (this.status === s.pending) {
        this.status = s.rejected;
        this.reason = reason;
        this.rejectCallbacks.forEach(cb => cb());
      }
    };
    
    cb(resolve, reject);
}
複製代碼

實現實例的then方法.

描述: 先思考一下, then方法, 要作哪些事情promise

  • then方法的使用. 接收兩個參數, 一個是成功時的回調, 另外一個是失敗是的回調. 但參數也能夠不是函數. 是一個普通的對象. 好比:
// then的使用
xx.then(result => {}, error => {})

xx.then('test', 'error occurrs')
複製代碼
  • 返回的仍是一個Promise. 能夠鏈式調用.
  • 執行then時, promise的狀態多是pending, fulfilled,rejected.
  • then的執行是一個異步. 須要想辦法模擬.

代碼以下:bash

then(resolve, reject) {
    // 對參數作檢查.
    // 可能的調用: .then('success', 'error')
    // 若是不是一個方法, 就使用默認的函數.進行值傳遞便可.
    const onResolved = typeof resolve === 'function' ? resolve : val => val;
    const onRejected = typeof reject === 'function' ? reject : err => {throw err};
    
    // 返回的是另外一個promise對象.
    const newPromise = new XPromise((resolve, reject) => {
      // 若是調用then的時候promise的狀態已經變爲完成.
      // 那麼調用成功的回調, 並傳遞參數.
      if (this.status === s.fulfilled) {
        // 使用setTimeout來模擬異步.
        setTimeout(() => {
          // 若是執行回調時, 發生異常.
          // 那麼就將異常做爲promise失敗的緣由.
          try {
            let result = onResolved(this.value);
    
            // 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
            resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }
    
      if (this.status === s.rejected) {
        // 使用setTimeout來模擬異步.
        setTimeout(() => {
          // 若是執行回調時, 發生異常.
          // 那麼就將異常做爲promise失敗的緣由.
          try {
            let result = onRejected(this.reason);
    
            // 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
            resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }
    
      // 若是調用then時狀態仍是pending. 說明promise執行
      // 內部的resolve或reject是異步的. 須要把then中的成功回調和失敗回調
      // 先存儲起來, 等等promise的狀態改成成功或失敗的時候再執行.
      if (this.status === s.pending) {
        this.resolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onResolved(this.value);
              resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0)
        });
    
        this.rejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
    
    return newPromise;
}

/** * 處理then中的返回結果. 方便鏈式調用. * @param {*} promise * @param {*} result * @param {*} resolve * @param {*} reject * @returns {XPromise} 返回一個promise. */
const resolvePromise = (promise, result, resolve, reject) => {
  // 參數判斷.
  // 判斷result和promise是否爲相同的引用.防止循環引用.
  if (promise === result) {
    return reject(new TypeError('循環引用了.'));
  }

  // 用來記錄promise的狀態是否改變過. 
  // 一旦改變就不能再次更改爲其餘的狀態.
  let isCalled;

  if(result !== null && 
    (typeof result === 'object' || typeof result === 'function')){
      try {
        const then = result.then;
        if(typeof then === 'function'){
          then.call(result, newResult => {
            if(isCalled)return;
            isCalled = true;

            // 這個newResult可能依舊是一個promise對象, 因此要遞歸調用.
            resolvePromise(promise, newResult, resolve, reject);
          }, error => {
            if(isCalled)return;
            isCalled = true;
            reject(error);
          });
        }else{
          resolve(result);
        }
      } catch (error) {
        if(isCalled)return;

        isCalled = true;
        reject(error);
      }
  }else{
    // 若是是一個普通值 那麼就直接把result做爲promise
    // 的成功的回調.
    resolve(result);
  }
};

複製代碼

實現實例的catch方法

描述: catch方法相對簡單,其實就是執行then函數的第二個參數.異步

catch(reject) {
    // 實際執行的是then的第二個參數.
    return this.then(null, reject);
}
複製代碼

實現實例的finally方法

描述: 其實就是then在只是成功和失敗的回調時都要執行.函數

finally(cb) {
    // then的resolve和reject都要執行.
    return this.then(cb, cb);
}
複製代碼

實現static resolve

描述: 返回一個promise, 執行成功的回調. 代碼實現以下, 有沒有以爲很是簡單.

static resolve(val) {
    return new XPromise((resolve) => resolve(val));
}
複製代碼

實現static reject

描述: 返回一個promise, 執行失敗的回調

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

實現static all

描述: 返回一個promise:

  • 若是參數中全部都執行成功,那麼此實例執行成功的回調
  • 若是參數中有一個執行失敗, 那麼此實例當即執行失敗的回調. 失敗的緣由是第一個promise失敗的結果.
static all(promises) {
    return new XPromise((resolve, reject) => {
      const arr = [];
    
      promises.forEach((p, i) => {
        p.then(data => {
          arr[i] = data;
        }, reject);
      });
    
      resolve(arr);
    });
}
複製代碼

實現static race

描述: 返回一個promise,一旦參數中某個promise執行成功或失敗, 就當即執行對於的回調.

static race(promises) {
    return new XPromise((resolve, reject) => {
      promises.forEach((p) => {
        // resolve後,狀態不能改變
        p.then(resolve, reject);
      });
    });
}
複製代碼

實現static allSettled

描述: 返回一個promise對象, 給定的全部promise都被成功解析或被失敗解析,而且每一個對象都描述每一個promise的結果.

static allSettled(promises) {
    return new XPromise((resolve, reject) => {
      const arr = [];
    
      promises.forEach((p, i) => {
        p.then(data => {
          arr[i] = data;
        }, reason => {
          arr[i] = reason;
        });
      });
    
      // 等待全部都執行完成後, 再返回.
      resolve(arr);
    });
}
複製代碼

實現static deferred

描述: 返回一個普通對象, 裏面包含一個promise.

static deferred() {
    const defer = {}
    defer.promise = new XPromise((resolve, reject) => {
      defer.resolve = resolve
      defer.reject = reject
    })
    return defer
}
複製代碼

到此, 一個Promise就實現了. 完整的代碼以下:

const s = {
  pending: 'pending',
  fulfilled: 'fulfilled',
  rejected: 'rejected'
};

/** * 出來then中的返回結果. 方便鏈式調用. * @param {*} promise * @param {*} result * @param {*} resolve * @param {*} reject * @returns {XPromise} 返回一個promise. */
const resolvePromise = (promise, result, resolve, reject) => {
  // 參數判斷.
  // 判斷result和promise是否爲相同的引用.防止循環引用.
  if (promise === result) {
    return reject(new TypeError('循環引用了.'));
  }

  // 用來記錄promise的狀態是否改變過. 
  // 一旦改變就不能再次更改爲其餘的狀態.
  let isCalled;

  if(result !== null && 
    (typeof result === 'object' || typeof result === 'function')){
      try {
        const then = result.then;
        if(typeof then === 'function'){
          then.call(result, newResult => {
            if(isCalled)return;
            isCalled = true;

            // 這個newResult可能依舊是一個promise對象, 因此要遞歸調用.
            resolvePromise(promise, newResult, resolve, reject);
          }, error => {
            if(isCalled)return;
            isCalled = true;
            reject(error);
          });
        }else{
          resolve(result);
        }
      } catch (error) {
        if(isCalled)return;

        isCalled = true;
        reject(error);
      }
  }else{
    // 若是是一個普通值 那麼就直接把result做爲promise
    // 的成功的回調.
    resolve(result);
  }
};

class XPromise {
  static resolve(val) {
    return new XPromise((resolve) => resolve(val));
  }

  static reject(reason) {
    return new XPromise((resolve, reject) => reject(reason));
  }

  /** * - 若是參數中全部都執行成功,那麼此實例執行成功的回調 * - 若是參數中有一個執行失敗, 那麼此實例當即執行失敗的回調. 失敗的緣由是第一個promise失敗的結果. * @param {Array} promises Promise集合. * @returns {XPromise} 返回一個promise. */
  static all(promises) {
    return new XPromise((resolve, reject) => {
      const arr = [];

      promises.forEach((p, i) => {
        p.then(data => {
          arr[i] = data;
        }, reject);
      });

      resolve(arr);
    });
  }

  static race(promises) {
    return new XPromise((resolve, reject) => {
      promises.forEach((p) => {
        // resolve後,狀態不能改變
        p.then(resolve, reject);
      });
    });
  }

  static allSettled(promises) {
    return new XPromise((resolve, reject) => {
      const arr = [];

      promises.forEach((p, i) => {
        p.then(data => {
          arr[i] = data;
        }, reason => {
          arr[i] = reason;
        });
      });

      // 等待全部都執行完成後, 再返回.
      resolve(arr);
    });
  }

  static deferred() {
    const defer = {}
    defer.promise = new XPromise((resolve, reject) => {
      defer.resolve = resolve
      defer.reject = reject
    })
    return defer
  }

  constructor(cb) {
    // 設置初始狀態爲pending.
    this.status = s.pending;

    // 存儲成功後的值.
    this.value = null;

    // 存儲失敗的緣由.
    this.reason = null;

    // 執行成功的回調蒐集.
    this.resolveCallbacks = [];

    // 執行失敗的回調蒐集
    this.rejectCallbacks = [];

    const resolve = val => {
      // 若是狀態是pending, 就更改.
      if (this.status === s.pending) {
        this.status = s.fulfilled;
        this.value = val;
        this.resolveCallbacks.forEach(cb => cb())
      }
    };

    const reject = reason => {
      if (this.status === s.pending) {
        this.status = s.rejected;
        this.reason = reason;
        this.rejectCallbacks.forEach(cb => cb());
      }
    };

    cb(resolve, reject);
  }

  then(resolve, reject) {
    // 對參數作檢查.
    // 可能的調用: .then('success', 'error')
    // 若是不是一個方法, 就使用默認的函數.進行值傳遞便可.
    const onResolved = typeof resolve === 'function' ? resolve : val => val;
    const onRejected = typeof reject === 'function' ? reject : err => {throw err};

    // 返回的是另外一個promise對象.
    const newPromise = new XPromise((resolve, reject) => {
      // 若是調用then的時候promise的狀態已經變爲完成.
      // 那麼調用成功的回調, 並傳遞參數.
      if (this.status === s.fulfilled) {
        // 使用setTimeout來模擬異步.
        setTimeout(() => {
          // 若是執行回調時, 發生異常.
          // 那麼就將異常做爲promise失敗的緣由.
          try {
            let result = onResolved(this.value);

            // 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
            resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      if (this.status === s.rejected) {
        // 使用setTimeout來模擬異步.
        setTimeout(() => {
          // 若是執行回調時, 發生異常.
          // 那麼就將異常做爲promise失敗的緣由.
          try {
            let result = onRejected(this.reason);

            // 調用resolvePromise函數, 更具result的值來決定newPromise的狀態.
            resolvePromise(newPromise, result, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      }

      // 若是調用then時狀態仍是pending. 說明promise執行
      // 內部的resolve或reject是異步的. 須要把then中的成功回調和失敗回調
      // 先存儲起來, 等等promise的狀態改成成功或失敗的時候再執行.
      if (this.status === s.pending) {
        this.resolveCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onResolved(this.value);
              resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0)
        });

        this.rejectCallbacks.push(() => {
          setTimeout(() => {
            try {
              const result = onRejected(this.reason);
              resolvePromise(newPromise, result, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });

    return newPromise;
  }

  catch(reject) {
    // 實際執行的是then的第二個參數.
    return this.then(null, reject);
  }

  finally(cb) {
    // then的resolve和reject都要執行.
    return this.then(cb, cb);
  }
}

module.exports = XPromise;
複製代碼

工具測試一下, 咱們的Promise是否符合promiseA+規範

  • 安裝promises-aplus-tests
npm install -D promises-aplus-tests 
複製代碼
  • 執行命令
npx promises-aplus-tests ./xpromise.js
複製代碼

測試結果以下:872個cases, 所有經過測試.

測試結果

demo

代碼

相關文章
相關標籤/搜索