相比於回調函數,Promise 解決了 「回調地獄」 和 「信任問題」 等痛點,而且大大提升了代碼的可讀性。在現代前端開發中,Promise 幾乎成了處理異步的首選(雖然還有更方便的 async/await,逃)。這篇文章從 Promise 的思想和運行機制入手,深刻理解每一個 API,最後手寫一個遵循 Promise/A+ 規範的 Promise 來。前端
JavaScript 異步方式共有有下面六種。git
事件監聽github
回調函數面試
發佈/訂閱ajax
Promise算法
生成器json
async/await小程序
面試中被問到 回調函數
有什麼缺點,相信你必定不假思索地回答 回調地獄
。的確如此,當咱們須要發送多個異步請求,而且每一個請求之間須要相互依賴時,就會產生回調地獄。微信小程序
前段時間寫了一個天氣微信小程序 Natsuha,它獲取天氣的邏輯大體以下(固然真實場景複雜的多)。api
首先要獲取用戶的經緯度 (接口 A)
根據經緯度反查城市 (接口 B)
根據城市拿到相應的天氣信息 (接口 C)
按照回調的方式去處理這個邏輯,大體會寫成下面的樣子:
ajax(A, () => {
// 獲取經緯度
ajax(B, () => {
// 根據經緯度反查城市
ajax(C, () => {
// 根據城市獲取天氣信息
});
});
});
複製代碼
看起來很醜陋不是嗎?相信你們對回調函數的缺點大體都瞭解,這裏就不展開,只作個總結。
代碼邏輯書寫順序與執行順序不一致,不利於閱讀與維護。
異步操做的順序變動時,須要大規模的代碼重構。
回調函數基本都是匿名函數,bug 追蹤困難。
回調函數是被第三方庫代碼(如上例中的 ajax )而非本身的業務代碼所調用的,形成了控制反轉(IoC)。
簡單談一談 控制反轉
,《你不知道的 JavaScript (中卷)》把回調函數的最大缺點歸結爲 信任問題
。例子中 ajax 是一個三方的函數(你徹底能夠把它想象成 jQuery 的 $.ajax()),咱們把本身的業務邏輯,也就是將回調函數 交給了
ajax 去處理。但 ajax 對咱們來講僅僅是一個黑盒,若是 ajax 自己有缺陷的話,咱們的回調函數就處於危險之中,這也就是所謂的「信任問題」。
不過 Promise 的出現解決了這些缺點,它可以把控制反轉再反轉回來。這樣的話,咱們能夠不把本身程序的傳給第三方,而是讓第三方給咱們提供瞭解其任務什麼時候結束的能力,進而由咱們本身的代碼來決定下一步作什麼。
《你不知道的 JavaScript (中卷)》舉了一個例子:
我在快餐店點了一個漢堡,並支付了 1.07 美金。這意味着我對某個值(漢堡)發出了請求。
接着收銀員給我一張 取餐單據
,它保證了我最終會獲得漢堡,所以 取餐單據
就是一個 承諾
。
在等待取餐的過程當中,我能夠作點其餘的事情,好比刷刷推特,看看 996.icu 今天又漲了多少 star。之因此我可作點兒其餘的事情,是由於 取餐單據
表明了我 將來的
漢堡。它在某種意義上已經成了漢堡的 佔位符
。從本質上來說,這個 佔位符
使得這個值再也不依賴時間,這是一個 將來值
。
終於,我聽到服務員在喊 250號前來取餐
,我就能夠拿着 取餐單據
換個人漢堡了。
可是可能還有另外一種結果,在我去取餐時,服務員充滿抱歉的告訴我漢堡已經售罄了,除了憤怒,咱們還能夠看到 將來值
可能成功,也可能失敗。
每一個 Promise 都會經歷一個短暫的生命週期:先是處於 進行中 (pending)
,此時操做還沒有完成,所以它也是 未處理 (unsettled)
的;一旦異步操做執行結束,Promise 變成 已處理 (settled)
狀態,此時它會進入到如下兩個狀態中的其中一個:
Fulfilled:Promise 異步操做成功完成
Rejected:因爲程序錯誤或其餘緣由,異步操做未能成功完成
Promise 自己是一個構造函數,它接收一個叫作 executor
的函數,該函數會被傳遞兩個名爲 resolve()
和 reject()
的函數做爲參數。resolve()
函數在執行器成功時被調用,而 reject()
在執行器操做失敗後被調用。看下面這個例子。
const fs = require('fs');
const promise = path =>
// 執行器接收 resolve() 和 reject() 做爲參數
new Promise((resolve, reject) => {
fs.readFile(__dirname + '/' + path, 'utf-8', (err, data) => {
if (err) {
// 失敗時調用 reject()
reject(err);
return;
}
// 成功時時調用 resolve()
resolve(data);
});
});
複製代碼
then() 方法接收兩個函數做爲參數,第一個做爲 完成
時的回調,第二個做爲 拒絕
時的回調。兩個參數均爲可選,所以你能夠只監聽 完成
,或者只監聽 拒絕
。其中當第一個參數爲 null
,第二個參數爲回調函數時,它意味着監聽 拒絕
。在實際應用中,完成
和 拒絕
都應當被監聽。
const promise = new Promise((resolve, reject) => {
resolve('success');
});
// 監聽完成和拒絕
promise.then(
res => {
// 完成
console.log(res);
},
e => {
// 拒絕
console.log(e);
},
);
// 只監聽完成
promise.then(res => {
console.log(res);
});
// 第一個參數爲 null 時意味着拒絕
promise.then(null, res => {
// 完成
console.log(res);
});
複製代碼
Promise 還有兩個方法分別是 catch()
和 finally()
,前者用於監聽 拒絕
,後者不管成功失敗都會被執行到。鏈式調用顯然可讀性更高,因此咱們推薦下面這種寫法。
promise
.then(res => {
console.log(res);
})
.catch(e => {
console.log(e);
})
.finally(() => {
console.log('不管成功失敗都會執行這句');
});
複製代碼
每次調用 then() 或 catch() 方法時都會 建立並返回一個新的 Promise
,只有當前一個 Promise 完成或被拒絕後,下一個纔會被解決。
看下面這個例子,p.then() 完成後返回第二個 Promise,接着又調用了它的 then() 方法,也就是說只有當第一個 Promise 被解決以後纔會調用第二個 then() 方法的 then()
。
let p = new Promise((resolve, reject) => {
resolve(42);
});
p.then(value => {
console.log(value); // 42
}).then(() => {
console.log('能夠執行到'); // '能夠執行到'
});
複製代碼
將上述示例拆開,看起來是這樣的。調用 p1.then() 的結果被存儲到 p2 中,p2.then() 被調用來添加最終的 then()
。
let p1 = new Promise((resolve, reject) => {
resolve(42);
});
let p2 = p1.then(value => {
console.log(value);
});
p2.then(() => {
console.log('能夠執行到');
});
複製代碼
咱們經過一個實例來看一下鏈式調用。下面是獲取城市天氣的場景:咱們首先須要調用 getCity
接口來獲取 城市id
,接着調用 getWeatherById/城市id
來獲取城市的天氣信息。首先用 Promise 封裝一個原生 Ajax。(敲黑板,面試可能要求手寫)
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject) {
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application/json');
client.send();
});
return promise;
};
const baseUrl = 'https://5cb322936ce9ce00145bf070.mockapi.io/api/v1';
複製代碼
經過鏈式調用來請求數據,最後別忘了捕獲錯誤。
getJSON(`${baseUrl}/getCity`)
.then(value => getJSON(`${baseUrl}/getWeatherById/${value.cityId}`))
.then(value => console.log(value))
.catch(e => {
console.log(e);
});
複製代碼
當 then() 方法或者 catch() 方法拋出錯誤時,鏈式調用的下一個 Promise 中的 catch() 方法能夠經過 catch()
接收這個錯誤。側面來說,異常不必定只發生在 Promise 中,還有可能發生在 then()
或者 catch()
中。
let p1 = new Promise((resolve, reject) => {
resolve(42);
});
p1.then(value => {
throw new Error(' `then()` 錯誤');
}).catch(e => {
console.log(e.message); // ' `then()` 錯誤'
});
複製代碼
不只 then()
能夠拋出異常,catch()
也能夠拋出的異常,且能夠被下一個 catch()
捕獲。所以,不管如何都應該在 Promise 鏈的末尾留一個 catch()
,以保證可以正確處理全部可能發生的錯誤。看下面這個例子。
let p1 = new Promise((resolve, reject) => {
throw new Error('執行器錯誤');
});
p1.catch(e => {
console.log(e.message); // '執行器錯誤'
throw new Error(' `catch()` 錯誤');
}).catch(e => {
console.log(e.message); // ' `catch()` 錯誤'
});
複製代碼
Promise 鏈的一個重要特性是能從一個 Promise 傳遞數據給下一個 Promise,經過完成處理函數的返回值,來將數據沿着一個鏈傳遞下去。咱們看下面這個例子。
function task() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('task');
}, 1000);
});
}
task()
.then(res => {
console.log(res);
return 'taskB';
})
.then(res => {
console.log(res);
return 'taskC';
})
.then(res => {
console.log(res);
throw new Error();
})
.catch(e => {
console.log(e);
return 'taskD';
})
.then(res => {
console.log(res);
});
複製代碼
運行結果如上圖所示。咱們知道,每次調用 then() 或者 catch() 都會返回一個新的 Promise 實例,經過指定處理函數的返回值,能夠沿着一個鏈繼續傳遞數據。
所以第一個 then() 將 'taskB' 做爲下一個 then() 的參數傳遞下去,一樣第二個 then() 將 'taskC' 做爲第三個 then() 的參數傳遞下去。
而第三個 then() 裏面拋出一個異常,上面說處處理函數中的拋出異常必定會被後面的拒絕處理函數捕獲,因此 catch() 裏可以打印出上一個 then() 的錯誤。
別忘了 catch() 返回 'taskD' 也能夠被最後一個 then() 捕獲。
Promise.resolve() 和 Promise.reject() 相似於快捷方式,用來建立一個 已完成
或 已被拒絕
的 promise。此外,Promise.resolve() 還能接受非 Promise 的 thenable
的做爲參數,也就是所謂 擁有 then 方法的對象
。
// p1 和 p2 等價
const p1 = new Promise((resolve, reject) => {
reject('Oops');
});
const p2 = Promise.reject('Oops');
// p3 和 p4 等價
const p3 = new Promise((resolve, reject) => {
resolve('Oops');
});
const p4 = Promise.resolve('Oops');
複製代碼
而對於 Promise.resolve(),它還能接收一個非 Promise 的 thenable
做爲參數。它能夠建立一個已完成的 Promise,也能夠建立一個以拒絕的 Promise。
let thenable1 = {
then(resolve, reject) {
resolve(1);
},
};
let p1 = Promise.resolve(thenable1);
p1.then(value => console.log(value)); // 1
let thenable2 = {
then(resolve, reject) {
reject(1);
},
};
let p2 = Promise.resolve(thenable2);
p2.catch(reason => console.log(reason)); // 1
複製代碼
該方法接收單個迭代對象(最多見的就是數組)做爲參數,並返回一個 Promise。這個可迭代對象的元素都是 Promise,只有在它們都完成後,所返回的 Promise 纔會被完成。
當全部的 Promise 均爲完成態,將會返回一個包含全部結果的數組。
只要有一個被拒絕,就不會返回數組,只會返回最早被拒絕的那個 Promise 的緣由
let p1 = new Promise((resolve, reject) => {
resolve(42);
});
let p2 = new Promise((resolve, reject) => {
reject(43);
});
let p3 = new Promise((resolve, reject) => {
reject(44);
});
let p4 = new Promise((resolve, reject) => {
resolve(45);
});
// 所有完成,返回數組
let p5 = Promise.all([p1, p4]);
p5.then(value => console.log(value)); // [42, 45]
// 只要有一個出錯,就不會返回數組,且只會返回最早被拒絕的那個 Promise 的緣由
let p6 = Promise.all([p1, p2, p3, p4]);
p6.catch(value => console.log(value)); // 43
複製代碼
該方法一樣接收單個迭代對象(最多見的就是數組)做爲參數,不一樣的是,該方法只要檢測到任意一個被解決,該方法就會作出響應。所以一個有趣的例子是把 請求接口
和一個 setTimeout
進行競逐,若是 setTimeout
先作出響應,就證實這個接口請求超時。
const p = Promise.race([
fetch('/some-api'),
new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('請求超時')), 3000);
}),
]);
p.then(value => {
console.log(value);
}).catch(reason => {
console.log(reason);
});
複製代碼
看起來 Promise 很美好,解決了回調函數的種種問題,但它也有本身的侷限性。
一旦建立一個 Promise 併爲其註冊完成/拒絕處理函數,Promise 將沒法被取消。
當處於 pending 狀態時,你沒法得知當前進展到哪一塊
由於 Promise 只能被決議一次(完成或拒絕),若是某些事件不斷髮生,stream 模式會更合適。
若是不設置回調函數,Promise 內部拋出的錯誤,不會反應到外部。
手撕代碼的以前能夠參照一下後面的 Promise A+ 規範翻譯,最好仍是本身去官網翻譯一遍,這樣寫起來纔會駕輕就熟。下面的代碼幾乎每句都加了註釋,而且連接到每一條規範。
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class Promise {
constructor(executor) {
// state 的初始狀態爲等待態
this.state = PENDING;
// 成功的值 (1.3)
this.value = undefined;
// 失敗的緣由 (1.5)
this.reason = undefined;
// 由於 then 在相同的 promise 能夠被調用屢次,因此須要將全部的 onFulfilled 存到數組 (2.2.6)
this.onResolvedCallbacks = [];
// 由於 then 在相同的 promise 能夠被調用屢次,因此須要將全部的 onRejected 存到數組 (2.2.6)
this.onRejectedCallbacks = [];
const resolve = value => {
// 只有當前是 pending,纔可能轉換爲 fulfilled
// 而且不能再轉換成其餘任何狀態,且必須擁有一個不可變的值
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
// onFulfilled 回調按原始調用順序依次執行 (2.2.6.1)
this.onResolvedCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
// 只有當前是 pending,纔可能轉換爲 rejected
// 而且不能再轉換成其餘任何狀態,且必須擁有一個不可變的緣由
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
// onRejectec 回調按原始調用順序依次執行 (2.2.6.1)
this.onRejectedCallbacks.forEach(fn => fn()); // (2.2.6.2)
}
};
// 若 executor 報錯,直接執行 reject()
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// onFulfilled 和 onRejected 都是可選參數 (2.2.1)
// 若是 onFulfilled 不是函數,則必須將它忽略 (2.2.1.1)
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value;
// 若是 onRejected 不是函數,則必須將它忽略 (2.2.1.2)
onRejected =
typeof onRejected === 'function'
? onRejected
: err => {
throw err;
};
// 爲了作到鏈式調用,規定每一個 then 方法必須返回一個 promise,稱爲 promise2
const promise2 = new Promise((resolve, reject) => {
// 在 promise 完成後方可調用 onFulfilled (2.2.2)
if (this.state === FULFILLED) {
// onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
setTimeout(() => {
try {
// value 做爲完成函數的第一個參數 (2.2.2.1)
// onFulfilled 函數被記作 x (2.2.7.1)
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
reject(e);
}
}, 0);
}
// 在 promise 被拒絕後方可調用 onRejected (2.2.3)
if (this.state === REJECTED) {
// onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
setTimeout(() => {
try {
// reason 做爲拒絕函數的第一個參數 (2.2.3.1)
// onRejected 函數被記作 x (2.2.7.1)
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
reject(e);
}
}, 0);
}
if (this.state === PENDING) {
this.onResolvedCallbacks.push(() => {
// onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// onFulfilled/onRejected 必須被異步調用,所以咱們用延時函數模擬 (2.2.4)
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
// 若是 onFulfilled/onRejected 拋出異常,則 promise2 必須拒絕執行,並返回拒因 e (2.2.7.2)
reject(e);
}
}, 0);
});
}
});
// 返回 promise2 (2.2.7)
return promise2;
}
// catch 實際是 then 的語法糖
catch(fn) {
return this.then(null, fn);
}
finally(fn) {
return this.then(
value => Promise.resolve(fn()).then(() => value),
reason =>
Promise.resolve(fn()).then(() => {
throw reason;
}),
);
}
}
const resolvePromise = (promise2, x, resolve, reject) => {
// 若是 promise 和 x 指向同一個對象,將以 TypeError 做爲拒因拒絕執行 promise (2.3.1)
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
// onFulfilled 和 onRejected 只能被調用一次,所以這裏加一個 flag 做爲判斷 (2.2.2.3 & 2.2.3.3)
let isCalled = false;
// 若是 x 是一個對象或者是一個函數 (2.3.3)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
// (2.3.3.1)
const then = x.then;
// 若是 then 是函數,就以 x 做爲 this 調用它 (2.3.3.2 & 2.3.3.3)
if (typeof then === 'function') {
// 後面接收兩個回調,第一個是成功的回調,第二個是失敗的回調 (2.3.3.3)
then.call(
x,
y => {
if (isCalled) return;
isCalled = true;
// 若是 resolvePromise 以 y 爲參數被調用,執行 [[Resolve]](promise, y) (2.3.3.3.1)
resolvePromise(promise2, y, resolve, reject);
},
r => {
if (isCalled) return;
isCalled = true;
// 若是 rejectPromise 以 r 爲緣由被調用,則以拒因 r 拒絕 promise (2.3.3.3.2)
reject(r);
},
);
} else {
// 若是 then 不是個函數,則以 x 爲參數執行 promise (2.3.3.4)
resolve(x);
}
} catch (e) {
if (isCalled) return;
isCalled = true;
// 若是取 x.then 報錯,則以 e 爲拒因拒絕 `promise` (2.3.3.2)
reject(e);
}
}
// 若是 then 不是個函數或者對象,則以 x 爲參數執行 promise (2.3.4)
else {
resolve(x);
}
};
// Promise.resolve
Promise.resolve = function(promises) {
if (promises instanceof Promise) {
return promises;
}
return new Promise((resolve, reject) => {
if (promises && promises.then && typeof promises.then === 'function') {
setTimeout(() => {
promises.then(resolve, reject);
});
} else {
resolve(promises);
}
});
};
// Promise.reject
Promise.reject = reason => new Promise((resolve, reject) => reject(reason));
// Promise.all
Promise.all = promises => {
return new Promise((resolve, reject) => {
let resolvedCounter = 0;
let promiseNum = promises.length;
let resolvedValues = new Array(promiseNum);
for (let i = 0; i < promiseNum; i += 1) {
(i => {
Promise.resolve(promises[i]).then(
value => {
resolvedCounter++;
resolvedValues[i] = value;
if (resolvedCounter === promiseNum) {
return resolve(resolvedValues);
}
},
reason => {
return reject(reason);
},
);
})(i);
}
});
};
//race方法
Promise.race = promises => {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
return;
} else {
for (let i = 0, l = promises.length; i < l; i += 1) {
Promise.resolve(promises[i]).then(
data => {
resolve(data);
return;
},
err => {
reject(err);
return;
},
);
}
}
});
};
複製代碼
最後全局安裝 yarn global add promises-aplus-tests
,插入下面這段代碼,而後使用 promises-aplus-tests 該文件的文件名
來驗證你手寫的 Promise 是否符合 Promises A+ 規範。
Promise.defer = Promise.deferred = function() {
let dfd = {};
dfd.promise = new Promise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = Promise;
複製代碼
一個開放、可靠且通用的 JavaScript Promise 標準。由開發者制定,供開發者參考。
promise 表明着一個異步操做的最終結果,與之交互的主要方式是它的 then
方法,該方法註冊了兩個回調函數,用於接收 promise 最終的值或者失敗的緣由。
該規範詳細描述了 then
方法的行爲,全部遵循 Promises/A+ 規範實現的 promise 都可以本標準做爲參照基礎來實施。所以,這份規範是很穩定的。雖然 Promises/A+ 組織偶爾會修訂這份規範,但大可能是爲了處理一些特殊的邊界狀況。這些改動都是微小且向下兼容的。若是咱們要進行大規模不兼容的更新,咱們必定會在事先進行謹慎地考慮、詳盡的探討和嚴格的測試。
最後,核心的 Promises/A+ 規範不會提供如何建立、解決和拒絕 promise,而是專一於提供一個通用的 then
方法。上述對於 promises 的操做方法未來在其餘規範中可能會說起。
1.1. 'promise' 是一個擁有 then
方法的對象或者函數,且其行爲符合此規範。
1.2. 'thenable' 是一個用來定義 then
方法的對象或者函數。
1.3. 'value' 是任何一個合法的 JavaScript 值 (包括 undefined
,thenable 或者 promise)
1.4. 'exception' 是一個使用 throw 語句拋出的值
1.5. 'reason' 代表了一個 promise 爲何會被拒絕
promise 必須是三個狀態之一:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。
2.1.1. 當前狀態爲 pending 時,一個 promise:
2.1.2. 當前狀態爲 fulfilled 時,一個 promise:
2.1.2.1 不能再轉換成其餘任何狀態
2.1.2.2 必須擁有一個不可變的值
2.1.3. 當前狀態爲 rejected 時,一個 promise:
2.1.3.1 不能再轉換成其餘任何狀態
2.1.3.2 必須擁有一個不可變的緣由
這裏的不可變指的是恆等(便可用 === 判斷相等),而不是意味着更深層次的不可變。(即當 value 或者 reason 爲引用類型時,只要求引用地址相等便可,但屬性值能夠被修改)
then
方法promise 必須提供一個 then
方法以訪問它當前或最終的值或被拒絕的緣由。
一個 promise 的 then
方法接收兩個參數:
promise.then(onFulfilled, onRejected);
複製代碼
2.2.1 onFulfilled
和 onRejected
都是可選參數。
2.2.1.1 若是 onFulfilled
不是個函數,它將被忽略
2.2.1.2 若是 onRejected
不是個函數,它將被忽略
2.2.2 若是 onFulfilled
是一個函數:
2.2.2.1 它必須在 promise
完成式後被調用,而且以 promise
的值做爲它的第一個參數。
2.2.2.2 在 promise
未完成前不可調用
2.2.2.3 此函數僅可調用一次
2.2.3 若是 onRejected
是一個函數:
2.2.3.1 它必須在 promise
被拒絕後被調用,而且以 promise
的緣由做爲它的第一個參數。
2.2.3.2 在 promise
未被拒絕前不可調用
2.2.3.3 此函數僅可調用一次
2.2.4 onFulfilled
和 onRejected
只有在 執行上下文 堆棧僅包含平臺代碼時纔可被調用。[1]
2.2.5 onFulfilled
和 onRejected
必須被做爲函數調用 (即沒有 this 值)。[2]
2.2.6 then
在相同的 promise 能夠被調用屢次
2.2.6.1 當 promise
是完成態, 全部相應的 onFulfilled
回調必須按其原始調用的順序執行。
2.2.6.2 當 promise
是拒絕態,全部相應的 onRejected
回調必須按其原始調用的順序執行。
2.2.7 每一個 then
方法必須返回一個 promise [3]。
promise2 = promise1.then(onFulfilled, onRejected);
複製代碼
2.2.7.1 若是 onFulfilled
或者 onRejected
返回一個值 x
,則運行下面的 Promise 解決過程:[[Resolve]](promise2, x)
2.2.7.2 若是 onFulfilled
或者 onRejected
拋出一個異常 e
,則 promise2
必須拒絕執行,並返回拒因 e
2.2.7.3 若是 onFulfilled
不是函數且 promise1
成功執行, promise2
必須成功執行並返回相同的值
2.2.7.4 若是 onRejected
不是函數且 promise1
拒絕執行, promise2
必須拒絕執行並返回相同的拒因
Promise 解決過程是一個抽象的操做,它接收一個 promise 和一個值,咱們能夠表示爲 [[Resolve]](promise, x)
,若是 x
是一個 thenable 的對象,解決程序將試圖接受 x
的狀態,不然用 x
的值來執行 promise
。
這種對 thenales 的處理使得 promise 的實現更加有普適性,只要它暴露出一個兼容 Promises/A+ 規範的 then
方法。它還容許讓遵循 Promise/A+ 規範的實現和不太規範但可用的實現良好共存。
爲了運行 [[Resolve]](promise, x)
,要執行下面的步驟:
2.3.1 若是 promise
和 x
指向同一個對象,將以 TypeError
做爲拒因拒絕執行 promise
。
2.3.2 若是 x
是一個 promise,那麼將 promise 將接受它的狀態 [4]:
2.3.2.1 若是 x
是等待態,promise
必須保留等待狀態直到 x
被完成或者被拒絕。
2.3.2.2 若是 x
是完成態,用相同的值執行 promise
2.3.2.3 若是 x
是拒態,用相同的緣由拒絕 promise
2.3.3 若是 x
是一個對象或者是一個函數,
2.3.3.1 把 x.then
賦值給 then
。[5]
2.3.3.2 若是取 x.then
的值時拋出錯誤 e
,則以 e
爲拒因拒絕 promise
2.3.3.3 若是 then
是函數,將 x
做爲函數的做用域 this
來調用它。傳遞兩個回調函數做爲參數,第一個參數叫作 resolvePromise
,第二個參數叫作 rejectPromise
:
2.3.3.3.1 若是 resolvePromise
以 y
爲參數被調用,執行 [[Resolve]](promise, y)
2.3.3.3.2 若是 rejectPromise
以 r
爲緣由被調用,則以拒因 r
拒絕 promise
2.3.3.3.3 若是 resolvePromise
和 rejectPromise
都被調用,或者被同一參數調用了屢次,則優先採用首次調用並忽略剩下的調用。
2.3.3.3.4 若是調用 then
拋出一個異常 e
2.3.3.3.4.1 若是 resolvePromise
和 rejectPromise
都被調用,則忽略掉它
2.3.3.3.4.2 不然,以 e
爲拒因拒絕這個 promise
2.3.3.4 若是 then
不是個函數,則以 x
爲參數執行 promise
2.3.4 若是 then
不是個函數或者對象,則以 x
爲參數執行 promise
若是一個 promise 被一個循環的 thenable 鏈中的對象解決,而 [[Resolve]](promise, thenable)
的遞歸性質又使得其被再次調用,根據上述的算法將會陷入無限遞歸之中。算法雖不強制要求,但也鼓勵施者檢測這樣的遞歸是否存在,若檢測到存在則以一個可識別的 TypeError 爲拒因來拒絕 promise [6]。
這裏的「平臺代碼」意味着引擎,環境和 promise 實施代碼,在實踐中要確保 onFulfilled
和 onRejected
異步執行,且應該在 then
方法被調用的那一輪事件循環以後的新執行棧中執行。這個事件隊列能夠採用「宏任務(macro-task)」機制,相似於 setTimeOut
或者 setImmediate
,也可使用「微任務(micro-task)」機制來實現,相似於 MutationObserver
或 process.nextTick
。由於 promise 實現被認爲是平臺代碼,因此它自己可能包含一個任務調度隊列或跳板,在其中調用處理程序。 ↩︎
在嚴格模式下 this
爲 undefined
,而在非嚴格模式中,this
爲全局對象。 ↩︎
代碼實如今知足全部要求的狀況下能夠容許 promise2 === promise1
。每一個實現都要文檔說明其是否容許以及在何種條件下容許 promise2 === promise1
。 ↩︎
整體來講,若是 x
符合當前實現,咱們才認爲它是真正的 promise 。這一規則容許那些特例實現接受符合已知要求的 Promises 狀態。 ↩︎
這步咱們先是存儲了一個指向 x.then
的引用,而後測試並調用該引用,以免屢次訪問 x.then
屬性。這種預防措施確保了該屬性的一致性,由於其值可能在檢索調用時被改變。 ↩︎
實現不該該對 thenable 鏈的深度設限,並假定超出本限制的遞歸就是無限循環。只有真正的循環遞歸才應能致使 TypeError
異常;若是一條無限長的鏈上 thenable 均不相同,那麼遞歸下去永遠是正確的行爲。 ↩︎