[實踐系列] 主要是讓咱們經過實踐去加深對一些原理的理解。前端
[實踐系列]前端路由git
[實踐系列]Babel原理github
[實踐系列]瀏覽器緩存web
有興趣的同窗能夠關注 [實踐系列] 。 求star求follow~算法
Promise是JS異步編程中的重要概念,異步抽象處理對象,是目前比較流行Javascript異步編程解決方案之一npm
爲實現者提供一個健全的、可互操做的 JavaScript promise 的開放標準。編程
解決 (fulfill) : 指一個 promise 成功時進行的一系列操做,如狀態的改變、回調的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之。json
拒絕(reject) : 指一個 promise 失敗時進行的一系列操做。promise
拒因 (reason) : 也就是拒絕緣由,指在 promise 被拒絕時傳遞給拒絕回調的值。瀏覽器
終值(eventual value) : 所謂終值,指的是 promise 被解決時傳遞給解決回調的值,因爲 promise 有一次性的特徵,所以當這個值被傳遞時,標誌着 promise 等待態的結束,故稱之終值,有時也直接簡稱爲值(value)。
Promise : promise 是一個擁有 then 方法的對象或函數,其行爲符合本規範。
thenable : 是一個定義了 then 方法的對象或函數,文中譯做「擁有 then 方法」。
異常(exception) : 是使用 throw 語句拋出的一個值。
下面咱們先來說述Promise/A+ 規範的幾個基本要求。
一個Promise的當前狀態必須是如下三種狀態中的一種: 等待狀態(Pending) 執行狀態(Fulfilled) 和 拒絕狀態(Rejected)。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
複製代碼
等待狀態 (Pending)
處於等待態時,promise 需知足如下條件:
if (this.state === PENDING) {
this.state = FULFILLED || REJECTED ;
}
複製代碼
執行狀態 (Fulfilled)
處於執行態時,promise 需知足如下條件:
不能遷移至其餘任何狀態
必須擁有一個不可變的終值
this.value = value;
複製代碼
拒絕狀態 (Rejected)
處於拒絕態時,promise 需知足如下條件:
不能遷移至其餘任何狀態
必須擁有一個不可變的據因
this.reason = reason;
複製代碼
這裏的不可變指的是恆等(便可用 === 判斷相等),而不是意味着更深層次的不可變(譯者注:蓋指當 value 或 reason 不是基本值時,只要求其引用地址相等,但屬性值可被更改)
一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因。
promise 的 then 方法接受兩個參數:
promise.then(onFulfilled, onRejected)
複製代碼
參數可選
onFulfilled 和 onRejected 都是可選參數。
若是 onFulfilled 不是函數,其必須被忽略
若是 onRejected 不是函數,其必須被忽略
onFulfilled 特性
若是 onFulfilled 是函數:
當 promise 執行結束後其必須被調用,其第一個參數爲 promise 的終值
在 promise 執行結束前其不可被調用
其調用次數不可超過一次
onRejected 特性
若是 onRejected 是函數:
當 promise 被拒絕執行後其必須被調用,其第一個參數爲 promise 的據因
在 promise 被拒絕執行前其不可被調用
其調用次數不可超過一次
調用時機
onFulfilled 和 onRejected 只有在執行環境堆棧僅包含平臺代碼時纔可被調用 注1
注1 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
這個事件隊列能夠採用「宏任務(macro - task)」機制或者「微任務(micro - task)」機制來實現。
因爲 promise 的實施代碼自己就是平臺代碼(譯者注:即都是 JavaScript),故代碼自身在處理在處理程序時可能已經包含一個任務調度隊列。
調用要求
onFulfilled 和 onRejected 必須被做爲函數調用(即沒有 this 值)
屢次調用
then 方法能夠被同一個 promise 調用屢次
當 promise 成功執行時,全部 onFulfilled 需按照其註冊順序依次回調
當 promise 被拒絕執行時,全部的 onRejected 需按照其註冊順序依次回調
咱們先經過實踐一個簡易版的Promise來消化一下上面Promises/A+規範的基本要求。
首先
npm init
// 測試實現是否符合 promises/A+ 規範
npm install promises-aplus-tests -D
複製代碼
package.json
{
"name": "ajpromise",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "promises-aplus-tests ./simple.js"
},
"author": "webfansplz",
"license": "MIT",
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
}
}
複製代碼
simple.js
//Promise 的三種狀態 (知足要求 -> Promise的狀態)
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class AjPromise {
constructor(fn) {
//當前狀態
this.state = PENDING;
//終值
this.value = null;
//拒因
this.reason = null;
//成功態回調隊列
this.onFulfilledCallbacks = [];
//拒絕態回調隊列
this.onRejectedCallbacks = [];
//成功態回調
const resolve = value => {
// 使用macro-task機制(setTimeout),確保onFulfilled異步執行,且在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。
setTimeout(() => {
if (this.state === PENDING) {
// pending(等待態)遷移至 fulfilled(執行態),保證調用次數不超過一次。
this.state = FULFILLED;
// 終值
this.value = value;
this.onFulfilledCallbacks.map(cb => {
this.value = cb(this.value);
});
}
});
};
//拒絕態回調
const reject = reason => {
// 使用macro-task機制(setTimeout),確保onRejected異步執行,且在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。 (知足要求 -> 調用時機)
setTimeout(() => {
if (this.state === PENDING) {
// pending(等待態)遷移至 fulfilled(拒絕態),保證調用次數不超過一次。
this.state = REJECTED;
//拒因
this.reason = reason;
this.onRejectedCallbacks.map(cb => {
this.reason = cb(this.reason);
});
}
});
};
try {
//執行promise
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
typeof onFulfilled === 'function' && this.onFulfilledCallbacks.push(onFulfilled);
typeof onRejected === 'function' && this.onRejectedCallbacks.push(onRejected);
// 返回this支持then 方法能夠被同一個 promise 調用屢次
return this;
}
}
複製代碼
就這樣,一個簡單的promise就完成了.
new AjPromise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 2000);
})
.then(res => {
console.log(res);
return res + 1;
})
.then(res => {
console.log(res);
});
//output
// delay 2s..
// 2
// 3
複製代碼
接下來,咱們來看看咱們的實現是否徹底符合promises/A+規範~
npm run test
複製代碼
GG,測試用例只過了一小部分,大部分飄紅~
OK,接下來,咱們來繼續瞭解promises/A+ 進一步的規範要求~
因爲接下來的要求比較抽象和難理解,因此咱們將一步一步實踐來加深理解。
1.then方法必須返回一個promise對象
2.若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
3.若是 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e。
4.若是 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值。
5.若是 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的據因。
6.不論 promise1 被 reject 仍是被 resolve 時 promise2 都會被 resolve,只有出現異常時纔會被 rejected。
咱們經過以上要求來一步一步完善then方法
1.
// 1.首先,then方法必須返回一個promise對象
then(onFulfilled, onRejected) {
let newPromise;
return (newPromise = new AjPromise((resolve, reject) => {}));
}
複製代碼
then(onFulfilled, onRejected) {
let newPromise;
return (newPromise = new AjPromise((resolve, reject) => {
// 2.若是 onFulfilled 或者 onRejected 返回一個值 x ,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
this.onFulfilledCallbacks.push(value => {
let x = onFulfilled(value);
//解決過程 resolvePromise
resolvePromise(newPromise, x);
});
this.onRejectedCallbacks.push(reason => {
let x = onRejected(reason);
//解決過程 resolvePromise
resolvePromise(newPromise, x);
});
}));
}
// 解決過程
function resolvePromise() {
//...
}
複製代碼
then(onFulfilled, onRejected) {
let newPromise;
return (newPromise = new AjPromise((resolve, reject) => {
// 3.若是 onFulfilled 或者 onRejected 拋出一個異常 e ,則 promise2 必須拒絕執行,並返回拒因 e。
this.onFulfilledCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(reason => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x);
} catch (e) {
reject(e);
}
});
}));
}
複製代碼
4,5.
then(onFulfilled, onRejected) {
let newPromise;
// 4.若是 onFulfilled 不是函數且 promise1 成功執行, promise2 必須成功執行並返回相同的值。
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 5.若是 onRejected 不是函數且 promise1 拒絕執行, promise2 必須拒絕執行並返回相同的據因。
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
return (newPromise = new AjPromise((resolve, reject) => {
this.onFulfilledCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(reason => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x);
} catch (e) {
reject(e);
}
});
}));
}
複製代碼
then(onFulfilled, onRejected) {
let newPromise;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
// 2.2.6規範 對於一個promise,它的then方法能夠調用屢次.
// 當在其餘程序中屢次調用同一個promise的then時 因爲以前狀態已經爲FULFILLED / REJECTED狀態,則會走如下邏輯,
// 因此要確保爲FULFILLED / REJECTED狀態後 也要異步執行onFulfilled / onRejected ,這裏使用setTimeout
// 6.不論 promise1 被 reject 仍是被 resolve 時 promise2 都會被 resolve,只有出現異常時纔會被 rejected。
// 因爲在接下來的解決過程當中須要調用resolve,reject進行處理,處理咱們在調用處理過程時,傳入參數
if (this.state == FULFILLED) {
return (newPromise = new AjPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
if (this.state == REJECTED) {
return (newPromise = new AjPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
if (this.state === PENDING) {
return (newPromise = new AjPromise((resolve, reject) => {
this.onFulfilledCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(reason => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
}
複製代碼
ok,完整的then方法搞定了。相信經過以上實踐,你對返回要求已經有了更深的理解。
Promise 解決過程是一個抽象的操做,其需輸入一個 promise 和一個值,咱們表示爲 [[Resolve]](promise, x),若是 x 有 then 方法且看上去像一個 Promise ,解決程序即嘗試使 promise 接受 x 的狀態;不然其用 x 的值來執行 promise 。
這種 thenable 的特性使得 Promise 的實現更具備通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法便可;這同時也使遵循 Promise/A+ 規範的實現能夠與那些不太規範但可用的實現能良好共存。
運行 [[Resolve]](promise, x) 需遵循如下步驟:
1。x 與 promise 相等
若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise。
2。x 爲 Promise
若是 x 爲 Promise ,則使 promise 接受 x 的狀態。
若是 x 處於等待態, promise 需保持爲等待態直至 x 被執行或拒絕。
若是 x 處於執行態,用相同的值執行 promise。
若是 x 處於拒絕態,用相同的據因拒絕 promise。
3。x 爲對象或函數
若是 x 爲對象或者函數:
把 x.then 賦值給 then。
若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise。
若是 then 是函數,將 x 做爲函數的做用域 this 調用之。傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise:
若是 x 不爲對象或者函數,以 x 爲參數執行 promise
若是一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable) 的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 爲據因來拒絕 promise 。
1.x 與 promise 相等
function resolvePromise(promise2, x, resolve, reject) {
//x 與 promise 相等
//若是從onFulfilled中返回的x 就是promise2 就會致使循環引用報錯
//若是 promise 和 x 指向同一對象,以 TypeError 爲據因拒絕執行 promise
if (x === promise2) {
reject(new TypeError('循環引用'));
}
}
複製代碼
2.x 爲 Promise。
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('循環引用'));
}
// x 爲 Promise
else if (x instanceof AjPromise) {
// 若是 x 爲 Promise ,則使 promise 接受 x 的狀態
// 若是 x 處於等待態, promise 需保持爲等待態直至 x 被執行或拒絕
if (x.state === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
} else {
// 若是 x 處於執行態,用相同的值執行 promise
// 若是 x 處於拒絕態,用相同的據因拒絕 promise
x.then(resolve, reject);
}
}
}
複製代碼
3.x 爲對象或函數
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('循環引用'));
}
if (x instanceof AjPromise) {
if (x.state === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
} else {
x.then(resolve, reject);
}
} else if (x && (typeof x === 'function' || typeof x === 'object')) {
// 避免屢次調用
let called = false;
try {
//把 x.then 賦值給 then
let then = x.then;
if (typeof then === 'function') {
// 若是 then 是函數,將 x 做爲函數的做用域 this 調用之。
// 傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise ,第二個參數叫作 rejectPromise
// 若是 resolvePromise 和 rejectPromise 均被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用
then.call(
x,
// 若是 resolvePromise 以值 y 爲參數被調用,則運行[[Resolve]](promise, y)
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
// 若是 rejectPromise 以據因 r 爲參數被調用,則以據因 r 拒絕 promise
r => {
if (called) return;
called = true;
reject(r);
}
);
}else {
// 若是 then 不是函數,以 x 爲參數執行 promise
resolve(x);
}
} catch (e) {
// 若是取 x.then 的值時拋出錯誤 e ,則以 e 爲據因拒絕 promise
// 若是調用 then 方法拋出了異常 e:
// 若是 resolvePromise 或 rejectPromise 已經被調用,則忽略之
// 不然以 e 爲據因拒絕 promise
if (called) return;
called = true;
reject(e);
}
} else {
// 若是 x 不爲對象或者函數,以 x 爲參數執行 promise
resolve(x);
}
}
複製代碼
Ok~比較複雜的解決過程也讓咱們搞定了.接下來咱們整合下代碼
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class AjPromise {
constructor(fn) {
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (value instanceof Promise) {
return value.then(resolve, reject);
}
setTimeout(() => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.map(cb => {
cb = cb(this.value);
});
}
});
};
const reject = reason => {
setTimeout(() => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.map(cb => {
cb = cb(this.reason);
});
}
});
};
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
then(onFulfilled, onRejected) {
let newPromise;
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
if (this.state === FULFILLED) {
return (newPromise = new AjPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
if (this.state === REJECTED) {
return (newPromise = new AjPromise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
if (this.state === PENDING) {
return (newPromise = new AjPromise((resolve, reject) => {
this.onFulfilledCallbacks.push(value => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
this.onRejectedCallbacks.push(reason => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
}));
}
}
}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
reject(new TypeError('循環引用'));
}
if (x instanceof AjPromise) {
if (x.state === PENDING) {
x.then(
y => {
resolvePromise(promise2, y, resolve, reject);
},
reason => {
reject(reason);
}
);
} else {
x.then(resolve, reject);
}
} else if (x && (typeof x === 'function' || typeof x === 'object')) {
let called = false;
try {
let then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
AjPromise.deferred = function() {
let defer = {};
defer.promise = new AjPromise((resolve, reject) => {
defer.resolve = resolve;
defer.reject = reject;
});
return defer;
};
module.exports = AjPromise;
複製代碼
再來看看咱們的實現是否符合Promises/A+規範
npm run test
複製代碼
nice,測試用例所有經過!
若是以爲有幫助到你,請給個star支持下做者~