如何實現一個經過 Promise/A+ 規範的 Promise

Promise

  • Promise 是異步編程的一種解決方案,比傳統的異步解決方案【回調函數】和【事件】更合理、更強大。現已被 ES6 歸入進規範中;
  • Promise 解決了「回調地獄」的痛點;

基本 API

屬性

  • Promise.length
    • length屬性,其值老是爲 1 (構造器參數的數目).
  • Promise.prototype
    • 表示 Promise 構造器的原型.

類方法

  • Promise.resolve(value)
    • 返回一個狀態由給定value決定的Promise對象。
    • 若是該值是thenable(即,帶有then方法的對象),返回的Promise對象的最終狀態由 then 方法執行決定;
    • 不然的話(該value爲空,基本類型或者不帶then方法的對象),返回的Promise對象狀態爲fulfilled(resolved),而且將該value傳遞給對應的then方法;
    • 一般而言,若是你不知道一個值是不是Promise對象,使用Promise.resolve(value) 來返回一個Promise對象,這樣就能將該valuePromise對象形式使用;
  • Promise.reject(reason)
    • 返回一個狀態爲失敗的Promise對象,並將給定的失敗信息傳遞給對應的處理方法
  • Promise.race(iterable)
    • iterable參數裏的任意一個子promise被成功或失敗後(即第一個執行完的promise),父promise立刻也會用子promise的成功返回值或失敗詳情做爲參數調用父promise綁定的相應句柄,並返回該promise對象
  • Promise.allSettled(iterable)
    • 返回一個在全部給定的 promise 已被 resolved 或被 rejected 後決議的 promise,並帶有一個對象數組,每一個對象表示對應的promise結果
  • Promise.all(iterable)
    • 這個方法返回一個新的promise對象,
    • promise對象在iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功
    • 一旦有任何一個iterable裏面的promise對象失敗則當即觸發該promise對象的失敗
    • 這個新的promise對象在觸發成功狀態之後,會把一個包含iterable裏全部promise返回值的數組做爲成功回調的返回值,順序跟iterable的順序保持一致;
    • 若是這個新的promise對象觸發了失敗狀態,它會把iterable裏第一個觸發失敗的promise對象的錯誤信息做爲它的失敗錯誤信息。
    • Promise.all方法常被用於處理多個promise對象的狀態集合。

原型

原型屬性

  • Promise.prototype.constructor
    • 返回被建立的實例函數. 默認爲 Promise 函數
    • 構造函數只執行一次

原型方法

  • Promise.prototype.then(onFulfilled, onRejected)
    • 添加解決(fulfillment)和拒絕(rejection)回調到當前 promise, 返回一個新的 promise, 將以回調的返回值來 resolve;
    • onRejected 捕獲上一個 then 拋出的異常,不會捕獲當前 onFulfilled 中拋出的異常;
  • Promise.prototype.catch(onRejected)
    • 添加一個拒絕(rejection) 回調到當前 promise, 返回一個新的promise
    • 當這個回調函數被調用,新 promise 將以它的返回值來 resolve,不然若是當前promise 進入fulfilled狀態,則以當前promise的完成結果做爲新promise的完成結果.
  • Promise.prototype.finally(onFinally)
    • 添加一個事件處理回調於當前promise對象,而且在原promise對象解析完畢後,返回一個新的promise對象。
    • 回調會在當前promise運行完畢後被調用,不管當前promise的狀態是完成(fulfilled)仍是失敗(rejected)

核心要點

process.nextTickpromise.then 都屬於 微任務(microtask),而 setImmediatesetTimeoutsetInterval 屬於 宏任務(macrotask),事件循環先執行宏任務,再執行全部微任務 • Promise 構造函數是同步執行的,promise.then 中的函數是異步執行的javascript

const promise = new Promise((resolve, reject) => {
  // 構造函數同步執行
  console.log(1);
  resolve();
  console.log(2);
});
promise.then(() => {
  // 異步執行
  console.log(3);
});
console.log(4);

// ------------執行結果-------------
1;
2;
4;
3;
複製代碼
  • promise 有 3 種狀態:pendingresolved(fulfilled)rejected。狀態改變只能是 pending->resolved 或者 pending->rejected,狀態一旦改變則不能再變。
    • pending: 初始狀態,既不是成功,也不是失敗狀態。
    • resolved(fulfilled): 意味着操做成功完成。
    • rejected: 意味着操做失敗。
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
});
const p2 = promise1.then(() => {
  throw new Error('error!!!')
});
console.log('promise1', p1);
console.log('promise2', p2);
setTimeout(() => {
  console.log('promise1', p1)
  console.log('promise2', p2)
}, 2000);

// ------------執行結果-------------
promise1 Promise {<pending>}
promise2 Promise {<pending>}
Uncaught (in promise) Error: error!!!
    at <anonymous>:7:9
Promise.then (async)
promise1 Promise {<resolved>: "success"}
promise2 Promise {<rejected>: Error: error!!!
    at <anonymous>:7:9}
複製代碼
  • 構造函數中的 resolvereject 只有第一次執行有效,屢次調用沒有任何做用
new Promise((resolve, reject) => {
  resolve("resolve1");
  reject("reject");
  resolve("resolve2");
})
  .then((res) => {
    console.log("then: ", res);
  })
  .catch((err) => {
    console.log("catch: ", err);
  });

// ------------執行結果-------------
then: resolve1;
複製代碼
  • promise 每次調用 .then 或者.catch 都會返回一個新的 promise,從而實現了鏈式調用
const p1 = Promise.resolve(1); // 返回 Promise
// 返回 Promise
const p2 = p1
  .then((res) => {
    console.log(res);
    return 2;
  })
  .catch((err) => {
    return 3;
  });
// 返回 Promise
const p3 = p2.then((res) => {
  console.log(res);
});

// ------------執行結果-------------
1;
2;
複製代碼
  • promise.then 或者 .catch 能夠被調用屢次,但這裏 Promise 構造函數只執行一次。
    • promise 內部狀態一經改變,而且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值
const p5 = new Promise((resolve, reject) => {
  // 構造函數只執行一次
  setTimeout(() => {
    console.log('start')
    resolve('success'); // 第一次生效
        resolve('success1'); // 執行無效
  }, 1000)
})
p5.then((res) => {
  console.log('promise1', res);
});
p5.then((res) => {
  console.log('promise2', res)
});

// ------------執行結果-------------
start
promise1 success
promise2 success
複製代碼
  • .then 能夠接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。
    • .catch.then 第二個參數的簡便寫法;
    • .then 的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,然後續的 .catch 能夠捕獲以前的錯誤
Promise.resolve().then(function success1 (res) {
    throw new Error('error'); // 第一個 then 拋出 error 下一個 then 才能接收
  }, function fail1 (e) {
    console.error('error1: ', e)
}).then(function success2 (res) {}, function fail2 (e) {
    // 接收上一個 then 拋出的 error
    console.error('error2: ', e)
});

// ------------執行結果-------------
error2:  Error: error
    at success1 (<anonymous>:2:11) 複製代碼
  • .then 或者 .catch 的參數指望是函數,傳入非函數則會發生值穿透
Promise.resolve(1)
  .then(2) // 這一步會穿透
  .then(Promise.resolve(3)) // 這裏 Promise.resolve(3) 爲 Promise 對象,這一步也會穿透
  .then(console.log); // 這裏接收到第一次 resolve 的 1

// ------------執行結果-------------
1;
複製代碼

造輪子

結構

function Promise(fn) {}

// 中斷拋出結果集
Promise.resolve = function (value) {};
// 中斷拋出異常
Promise.reject = function (reason) {};
// 返回多個promise集合請求中最快返回的結果
Promise.race = function (iterable) {};
// 返回多個promise集合全部不論正常或者異常的結果集
Promise.allSettled = function (iterable) {};
// 返回多個promise集合全部正常的結果集,有錯誤則中斷返回該錯誤結果
Promise.all = function (iterable) {};

// 原型方法 then, 返回新的promise造成鏈式調用
Promise.prototype.then = function (onResolved, onRejected) {};
// 原型方法 catch, 拋出異常
Promise.prototype.catch = function (onError) {};
// 原型方法 promise 正常或者異常以後的最後一次處理
Promise.prototype.finally = function (onFinally) {};
複製代碼

Promise 構造函數

function resolve(value) {
  var _this = this;

  // 狀態不爲 pending 則不執行,這裏避免屢次觸發
  if (_this._status !== "pending") return;

  setTimeout(function () {
    _this._status = "fulfilled";
    _this._value = value;
    _this._onResolvedFns.forEach(function (cb) {
      cb && cb();
    });
  });
}

function reject(error) {
  var _this = this;

  // 狀態不爲 pending 則不執行,這裏避免屢次觸發
  if (_this._status !== "pending") return;

  setTimeout(function () {
    _this._status = "rejected";
    _this._error = error;
    _this._onRejectedFns.forEach(function (cb) {
      cb && cb();
    });
  });
}

function isFunction(func) {
  return typeof func === "function";
}

function isObject(func) {
  return typeof func === "object";
}

function Promise(fn) {
  if (!isObject(this)) {
    throw new TypeError("Promise 必須是 new 實例化的對象");
  }
  if (!isFunction(fn)) {
    throw new TypeError("Promise 構造函數入參必須是函數");
  }

  // 狀態 pending/fulfilled/rejected
  this._status = "pending"; // 默認 pending
  this._value = null; // 值
  this._error = null; // 異常

  // 成功的回調
  this._onResolvedFns = [];
  // 失敗的回調
  this._onRejectedFns = [];

  try {
    // 綁定當前上下文
    fn(resolve.bind(this), reject.bind(this));
  } catch (e) {
    reject(e);
  }
}
複製代碼

Promise.prototype.then 鏈式回調

/** * 解析 promise * 若回調爲 promise 實例,則繼續流式解析 * * @param {*} promise * @param {*} result 回調結果 * @param {*} resolve * @param {*} reject */
function resolvePromise(promise, result, resolve, reject) {
  // 循環引用檢測
  if (promise === result) return reject(new TypeError("循環引用"));

  if (result instanceof Promise) {
    result.then(function (newResult) {
      resolvePromise(promise, newResult, resolve, reject);
    }, reject);
  } else if (isObject(result) || isFunction(result)) {
    if (result === null) return resolve(result);

    var then;

    try {
      then = result.then;
    } catch (error) {
      return reject(error);
    }

    if (!isFunction(then)) return resolve(result);

    var called = false; // 調用鎖

    try {
      var _thenLock = function (cb) {
        // 防止再次調用
        if (called) return;
        called = true; // 標記鎖
        cb && cb();
      };

      // then 流式調用
      then.call(
        result,
        function (nextResult) {
          _thenLock(function () {
            resolvePromise(promise, nextResult, resolve, reject);
          });
        },
        function (r) {
          //只要失敗了就失敗了
          _thenLock(function () {
            reject(r);
          });
        }
      );
    } catch (e) {
      _thenLock(function () {
        reject(e);
      });
    }
  } else {
    resolve(result);
  }
}

// 原型方法 then, 返回新的promise造成鏈式調用
Promise.prototype.then = function (onResolved, onRejected) {
  // then 接收兩個函數,若果不是函數則直接形成值穿透,即上一個 then 的值繼續向下走
  onResolved = isFunction(onResolved)
    ? onResolved
    : function (y) {
        return y;
      };
  onRejected = isFunction(onRejected)
    ? onRejected
    : function (err) {
        throw err;
      };

  var _this = this;

  var promise = new Promise(function (resolve, reject) {
    if (_this._status === "pending") {
      // pending 狀態
      // 存放成功回調
      _this._onResolvedFns.push(function () {
        setTimeout(function () {
          try {
            resolvePromise(promise, onResolved(_this._value), resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      });
      // 存放失敗的回調
      _this._onRejectedFns.push(function () {
        setTimeout(function () {
          try {
            resolvePromise(promise, onRejected(_this._error), resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      });
    } else {
      setTimeout(function () {
        try {
          // fulfilled / rejected 狀態 解析回調
          resolvePromise(
            promise,
            _this._status === "fulfilled"
              ? onResolved(_this._value)
              : onRejected(_this._error),
            resolve,
            reject
          );
        } catch (error) {
          reject(error);
        }
      });
    }
  });

  return promise;
};
複製代碼

Promise.prototype.catch 異常捕獲

// 原型方法 catch, 拋出異常
Promise.prototype.catch = function (onError) {
  // catch 方法就是then方法沒有成功的簡寫
  return this.then(null, onError);
};
複製代碼

Promise.prototype.finally 結束調用

// 原型方法 promise 正常或者異常以後的最後一次處理
Promise.prototype.finally = function (onFinally) {
  var _finally = Promise.resolve(onFinally());

  return this.then(
    function (value) {
      return _finally.then(function () {
        return value;
      });
    },
    function (error) {
      return _finally.then(function () {
        throw error;
      });
    }
  );
};
複製代碼

Promise.resolve

// 中斷拋出結果集
Promise.resolve = function (value) {
  return new Promise(function (resolve) {
    resolve(value);
  });
};
複製代碼

Promise.reject

// 中斷拋出異常
Promise.reject = function (reason) {
  return new Promise(function (resolve, reject) {
    reject(reason);
  });
};
複製代碼

Promise.all

// 返回多個promise集合全部正常的結果集,有錯誤則中斷返回該錯誤結果
Promise.all = function (iterable) {
  return new Promise(function (resolve, reject) {
    var array = Array.prototype.slice.call(iterable);
    if (!array.length) return resolve([]);

    var results = [];
    var count = 0;

    for (var i = 0; i < array.length; i++) {
      (function (i) {
        Promise.resolve(array[i])
          .then(function (res) {
            results[i] = res;
            count++;

            if (count === array.length) {
              resolve(results);
            }
          })
          .catch(function (error) {
            reject(error);
          });
      })(i);
    }
  });
};
複製代碼

Promise.race

// 返回多個promise集合請求中最快返回的結果
Promise.race = function (iterable) {
  return new Promise(function (resolve, reject) {
    // 淺拷貝
    var array = Array.prototype.slice.call(iterable);
    for (var i = 0; i < array.length; i++) {
      Promise.resolve(array[i]).then(resolve, reject);
    }
  });
};
複製代碼

Promise.allSettled

// 返回多個promise集合全部不論正常或者異常的結果集
Promise.allSettled = function (iterable) {
  return new Promise(function (resolve, reject) {
    var array = Array.prototype.slice.call(iterable);
    if (!array.length) return resolve([]);

    var results = [];
    var count = 0;

    for (var i = 0; i < array.length; i++) {
      (function (i) {
        Promise.resolve(array[i]).finally(function (value) {
          results[i] = res;
          count++;

          if (count === array.length) {
            resolve(results);
          }
        });
      })(i);
    }
  });
};
複製代碼

Promise/A+ 測試

  • 安裝 promises-tests 測試包
  • 編寫測試文件 promise.spec.js
var Promise = require("./promise");

Promise.deferred = function () {
  var result = {};
  result.promise = new Promise(function (resolve, reject) {
    result.resolve = resolve;
    result.reject = reject;
  });

  return result;
};

module.exports = Promise;
複製代碼
  • 執行測試腳本 promises-tests promise.spec.js
  • 查看用例執行成功

promise 用例測試結果

完整代碼

function resolve(value) {
  var _this = this;

  // 狀態不爲 pending 則不執行,這裏避免屢次觸發
  if (_this._status !== "pending") return;

  setTimeout(function () {
    _this._status = "fulfilled";
    _this._value = value;
    _this._onResolvedFns.forEach(function (cb) {
      cb && cb();
    });
  });
}

function reject(error) {
  var _this = this;

  // 狀態不爲 pending 則不執行,這裏避免屢次觸發
  if (_this._status !== "pending") return;

  setTimeout(function () {
    _this._status = "rejected";
    _this._error = error;
    _this._onRejectedFns.forEach(function (cb) {
      cb && cb();
    });
  });
}

function isFunction(func) {
  return typeof func === "function";
}

function isObject(func) {
  return typeof func === "object";
}

/** * 解析 promise * 若回調爲 promise 實例,則繼續流式解析 * * @param {*} promise * @param {*} result 回調結果 * @param {*} resolve * @param {*} reject */
function resolvePromise(promise, result, resolve, reject) {
  // 循環引用檢測
  if (promise === result) return reject(new TypeError("循環引用"));

  if (result instanceof Promise) {
    result.then(function (newResult) {
      resolvePromise(promise, newResult, resolve, reject);
    }, reject);
  } else if (isObject(result) || isFunction(result)) {
    if (result === null) return resolve(result);

    var then;

    try {
      then = result.then;
    } catch (error) {
      return reject(error);
    }

    if (!isFunction(then)) return resolve(result);

    var called = false; // 調用鎖

    try {
      var _thenLock = function (cb) {
        // 防止再次調用
        if (called) return;
        called = true; // 標記鎖
        cb && cb();
      };

      // then 流式調用
      then.call(
        result,
        function (nextResult) {
          _thenLock(function () {
            resolvePromise(promise, nextResult, resolve, reject);
          });
        },
        function (r) {
          //只要失敗了就失敗了
          _thenLock(function () {
            reject(r);
          });
        }
      );
    } catch (e) {
      _thenLock(function () {
        reject(e);
      });
    }
  } else {
    resolve(result);
  }
}

function Promise(fn) {
  if (!isObject(this)) {
    throw new TypeError("Promise 必須是 new 實例化的對象");
  }
  if (!isFunction(fn)) {
    throw new TypeError("Promise 構造函數入參必須是函數");
  }

  // 狀態 pending/fulfilled/rejected
  this._status = "pending"; // 默認 pending
  this._value = null; // 值
  this._error = null; // 異常

  // 成功的回調
  this._onResolvedFns = [];
  // 失敗的回調
  this._onRejectedFns = [];

  try {
    // 綁定當前上下文
    fn(resolve.bind(this), reject.bind(this));
  } catch (e) {
    reject(e);
  }
}

// 原型方法 then, 返回新的promise造成鏈式調用
Promise.prototype.then = function (onResolved, onRejected) {
  // then 接收兩個函數,若果不是函數則直接形成值穿透,即上一個 then 的值繼續向下走
  onResolved = isFunction(onResolved)
    ? onResolved
    : function (y) {
        return y;
      };
  onRejected = isFunction(onRejected)
    ? onRejected
    : function (err) {
        throw err;
      };

  var _this = this;

  var promise = new Promise(function (resolve, reject) {
    if (_this._status === "pending") {
      // pending 狀態
      // 存放成功回調
      _this._onResolvedFns.push(function () {
        setTimeout(function () {
          try {
            resolvePromise(promise, onResolved(_this._value), resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      });
      // 存放失敗的回調
      _this._onRejectedFns.push(function () {
        setTimeout(function () {
          try {
            resolvePromise(promise, onRejected(_this._error), resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      });
    } else {
      setTimeout(function () {
        try {
          // fulfilled / rejected 狀態 解析回調
          resolvePromise(
            promise,
            _this._status === "fulfilled"
              ? onResolved(_this._value)
              : onRejected(_this._error),
            resolve,
            reject
          );
        } catch (error) {
          reject(error);
        }
      });
    }
  });

  return promise;
};

// 原型方法 catch, 拋出異常
Promise.prototype.catch = function (onError) {
  // catch 方法就是then方法沒有成功的簡寫
  return this.then(null, onError);
};

// 原型方法 promise 正常或者異常以後的最後一次處理
Promise.prototype.finally = function (onFinally) {
  var _finally = Promise.resolve(onFinally());

  return this.then(
    function (value) {
      return _finally.then(function () {
        return value;
      });
    },
    function (error) {
      return _finally.then(function () {
        throw error;
      });
    }
  );
};

// 中斷拋出結果集
Promise.resolve = function (value) {
  return new Promise(function (resolve) {
    resolve(value);
  });
};

// 中斷拋出異常
Promise.reject = function (reason) {
  return new Promise(function (resolve, reject) {
    reject(reason);
  });
};

// 返回多個promise集合請求中最快返回的結果
Promise.race = function (iterable) {
  return new Promise(function (resolve, reject) {
    // 淺拷貝
    var array = Array.prototype.slice.call(iterable);
    for (var i = 0; i < array.length; i++) {
      Promise.resolve(array[i]).then(resolve, reject);
    }
  });
};

// 返回多個promise集合全部不論正常或者異常的結果集
Promise.allSettled = function (iterable) {
  return new Promise(function (resolve, reject) {
    var array = Array.prototype.slice.call(iterable);
    if (!array.length) return resolve([]);

    var results = [];
    var count = 0;

    for (var i = 0; i < array.length; i++) {
      (function (i) {
        Promise.resolve(array[i]).finally(function (value) {
          results[i] = res;
          count++;

          if (count === array.length) {
            resolve(results);
          }
        });
      })(i);
    }
  });
};

// 返回多個promise集合全部正常的結果集,有錯誤則中斷返回該錯誤結果
Promise.all = function (iterable) {
  return new Promise(function (resolve, reject) {
    var array = Array.prototype.slice.call(iterable);
    if (!array.length) return resolve([]);

    var results = [];
    var count = 0;

    for (var i = 0; i < array.length; i++) {
      (function (i) {
        Promise.resolve(array[i])
          .then(function (res) {
            results[i] = res;
            count++;

            if (count === array.length) {
              resolve(results);
            }
          })
          .catch(function (error) {
            reject(error);
          });
      })(i);
    }
  });
};

module.exports = Promise;
複製代碼

學習參考

小結

以上是對 promise 的學習紀要,但願可以幫助到你們,若有不對的地方,但願你們不吝指正,謝謝。java

相關文章
相關標籤/搜索