帶你寫出符合Promise/A+ 規(guī)范Promise的源碼
Promise是前端面試中的高頻問(wèn)題,如果你能根據(jù)PromiseA+的規(guī)范,寫出符合規(guī)范的源碼,那么我想,對(duì)于面試中的Promise相關(guān)的問(wèn)題,都能夠給出比較完美的答案。
我的建議是,對(duì)照規(guī)范多寫幾次實(shí)現(xiàn),也許第一遍的時(shí)候,是改了多次,才能通過(guò)測(cè)試,那么需要反復(fù)的寫,我已經(jīng)將Promise的源碼實(shí)現(xiàn)寫了不下七遍,不那么聰明的話,當(dāng)然需要更加努力啦~
Promise的源碼實(shí)現(xiàn)
- /**
- * 1. new Promise時(shí),需要傳遞一個(gè) executor 執(zhí)行器,執(zhí)行器立刻執(zhí)行
- * 2. executor 接受兩個(gè)參數(shù),分別是 resolve 和 reject
- * 3. promise 只能從 pending 到 rejected, 或者從 pending 到 fulfilled
- * 4. promise 的狀態(tài)一旦確認(rèn),就不會(huì)再改變
- * 5. promise 都有 then 方法,then 接收兩個(gè)參數(shù),分別是 promise 成功的回調(diào) onFulfilled,
- * 和 promise 失敗的回調(diào) onRejected
- * 6. 如果調(diào)用 then 時(shí),promise已經(jīng)成功,則執(zhí)行 onFulfilled,并將promise的值作為參數(shù)傳遞進(jìn)去。
- * 如果promise已經(jīng)失敗,那么執(zhí)行 onRejected, 并將 promise 失敗的原因作為參數(shù)傳遞進(jìn)去。
- * 如果promise的狀態(tài)是pending,需要將onFulfilled和onRejected函數(shù)存放起來(lái),等待狀態(tài)確定后,再依次將對(duì)應(yīng)的函數(shù)執(zhí)行(發(fā)布訂閱)
- * 7. then 的參數(shù) onFulfilled 和 onRejected 可以缺省
- * 8. promise 可以then多次,promise 的then 方法返回一個(gè) promise
- * 9. 如果 then 返回的是一個(gè)結(jié)果,那么就會(huì)把這個(gè)結(jié)果作為參數(shù),傳遞給下一個(gè)then的成功的回調(diào)(onFulfilled)
- * 10. 如果 then 中拋出了異常,那么就會(huì)把這個(gè)異常作為參數(shù),傳遞給下一個(gè)then的失敗的回調(diào)(onRejected)
- * 11.如果 then 返回的是一個(gè)promise,那么會(huì)等這個(gè)promise執(zhí)行完,promise如果成功,
- * 就走下一個(gè)then的成功,如果失敗,就走下一個(gè)then的失敗
- */
- const PENDING = 'pending';
- const FULFILLED = 'fulfilled';
- const REJECTED = 'rejected';
- function Promise(executor) {
- let self = this;
- self.status = PENDING;
- self.onFulfilled = [];//成功的回調(diào)
- self.onRejected = []; //失敗的回調(diào)
- //PromiseA+ 2.1
- function resolve(value) {
- if (self.status === PENDING) {
- self.status = FULFILLED;
- self.value = value;
- self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
- }
- }
- function reject(reason) {
- if (self.status === PENDING) {
- self.status = REJECTED;
- self.reason = reason;
- self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
- }
- }
- try {
- executor(resolve, reject);
- } catch (e) {
- reject(e);
- }
- }
- Promise.prototype.then = function (onFulfilled, onRejected) {
- //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
- onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
- onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
- let self = this;
- //PromiseA+ 2.2.7
- let promise2 = new Promise((resolve, reject) => {
- if (self.status === FULFILLED) {
- //PromiseA+ 2.2.2
- //PromiseA+ 2.2.4 --- setTimeout
- setTimeout(() => {
- try {
- //PromiseA+ 2.2.7.1
- let x = onFulfilled(self.value);
- resolvePromise(promise2, x, resolve, reject);
- } catch (e) {
- //PromiseA+ 2.2.7.2
- reject(e);
- }
- });
- } else if (self.status === REJECTED) {
- //PromiseA+ 2.2.3
- setTimeout(() => {
- try {
- let x = onRejected(self.reason);
- resolvePromise(promise2, x, resolve, reject);
- } catch (e) {
- reject(e);
- }
- });
- } else if (self.status === PENDING) {
- self.onFulfilled.push(() => {
- setTimeout(() => {
- try {
- let x = onFulfilled(self.value);
- resolvePromise(promise2, x, resolve, reject);
- } catch (e) {
- reject(e);
- }
- });
- });
- self.onRejected.push(() => {
- setTimeout(() => {
- try {
- let x = onRejected(self.reason);
- resolvePromise(promise2, x, resolve, reject);
- } catch (e) {
- reject(e);
- }
- });
- });
- }
- });
- return promise2;
- }
- function resolvePromise(promise2, x, resolve, reject) {
- let self = this;
- //PromiseA+ 2.3.1
- if (promise2 === x) {
- reject(new TypeError('Chaining cycle'));
- }
- if (x && typeof x === 'object' || typeof x === 'function') {
- let used; //PromiseA+2.3.3.3.3 只能調(diào)用一次
- try {
- let then = x.then;
- if (typeof then === 'function') {
- //PromiseA+2.3.3
- then.call(x, (y) => {
- //PromiseA+2.3.3.1
- if (used) return;
- used = true;
- resolvePromise(promise2, y, resolve, reject);
- }, (r) => {
- //PromiseA+2.3.3.2
- if (used) return;
- used = true;
- reject(r);
- });
- }else{
- //PromiseA+2.3.3.4
- if (used) return;
- used = true;
- resolve(x);
- }
- } catch (e) {
- //PromiseA+ 2.3.3.2
- if (used) return;
- used = true;
- reject(e);
- }
- } else {
- //PromiseA+ 2.3.3.4
- resolve(x);
- }
- }
- module.exports = Promise;
有專門的測(cè)試腳本可以測(cè)試所編寫的代碼是否符合PromiseA+的規(guī)范。
首先,在promise實(shí)現(xiàn)的代碼中,增加以下代碼:
- PromisePromise.defer = Promise.deferred = function () {
- let dfd = {};
- dfd.promise = new Promise((resolve, reject) => {
- dfd.resolve = resolve;
- dfd.reject = reject;
- });
- return dfd;
- }
安裝測(cè)試腳本:
- npm install -g promises-aplus-tests
如果當(dāng)前的promise源碼的文件名為promise.js
那么在對(duì)應(yīng)的目錄執(zhí)行以下命令:
- promises-aplus-tests promise.js
promises-aplus-tests中共有872條測(cè)試用例。以上代碼,可以完美通過(guò)所有用例。
對(duì)上面的代碼實(shí)現(xiàn)做一點(diǎn)簡(jiǎn)要說(shuō)明(其它一些內(nèi)容注釋中已經(jīng)寫得很清楚):
- onFulfilled 和 onFulfilled的調(diào)用需要放在setTimeout,因?yàn)橐?guī)范中表示: onFulfilled or onRejected must not be called until the execution context stack contains only platform code。使用setTimeout只是模擬異步,原生Promise并非是這樣實(shí)現(xiàn)的。
2. 在 resolvePromise 的函數(shù)中,為何需要usedd這個(gè)flag,同樣是因?yàn)橐?guī)范中明確表示: If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored. 因此我們需要這樣的flag來(lái)確保只會(huì)執(zhí)行一次。
3. self.onFulfilled 和 self.onRejected 中存儲(chǔ)了成功的回調(diào)和失敗的回調(diào),根據(jù)規(guī)范2.6顯示,當(dāng)promise從pending態(tài)改變的時(shí)候,需要按照順序去指定then對(duì)應(yīng)的回調(diào)。
PromiseA+的規(guī)范(翻譯版)
PS: 下面是我翻譯的規(guī)范,供參考
術(shù)語(yǔ)
- promise 是一個(gè)有then方法的對(duì)象或者是函數(shù),行為遵循本規(guī)范
- thenable 是一個(gè)有then方法的對(duì)象或者是函數(shù)
- value 是promise狀態(tài)成功時(shí)的值,包括 undefined/thenable或者是 promise
- exception 是一個(gè)使用throw拋出的異常值
- reason 是promise狀態(tài)失敗時(shí)的值
要求
2.1 Promise States
Promise 必須處于以下三個(gè)狀態(tài)之一: pending, fulfilled 或者是 rejected
2.1.1 如果promise在pending狀態(tài)
- 2.1.1.1 可以變成 fulfilled 或者是 rejected
2.1.2 如果promise在fulfilled狀態(tài)
- 2.1.2.1 不會(huì)變成其它狀態(tài)
- 2.1.2.2 必須有一個(gè)value值
2.1.3 如果promise在rejected狀態(tài)
- 2.1.3.1 不會(huì)變成其它狀態(tài)
- 2.1.3.2 必須有一個(gè)promise被reject的reason
概括即是:promise的狀態(tài)只能從pending變成fulfilled,或者從pending變成rejected.promise成功,有成功的value.promise失敗的話,有失敗的原因
2.2 then方法
promise必須提供一個(gè)then方法,來(lái)訪問(wèn)最終的結(jié)果
promise的then方法接收兩個(gè)參數(shù)
- promise.then(onFulfilled, onRejected)
2.2.1 onFulfilled 和 onRejected 都是可選參數(shù)
- 2.2.1.1 onFulfilled 必須是函數(shù)類型
- 2.2.1.2 onRejected 必須是函數(shù)類型
2.2.2 如果 onFulfilled 是函數(shù):
- 2.2.2.1 必須在promise變成 fulfilled 時(shí),調(diào)用 onFulfilled,參數(shù)是promise的value
- 2.2.2.2 在promise的狀態(tài)不是 fulfilled 之前,不能調(diào)用
- 2.2.2.3 onFulfilled 只能被調(diào)用一次
2.2.3 如果 onRejected 是函數(shù):
- 2.2.3.1 必須在promise變成 rejected 時(shí),調(diào)用 onRejected,參數(shù)是promise的reason
- 2.2.3.2 在promise的狀態(tài)不是 rejected 之前,不能調(diào)用
- 2.2.3.3 onRejected 只能被調(diào)用一次
2.2.4 onFulfilled 和 onRejected 應(yīng)該是微任務(wù)
2.2.5 onFulfilled 和 onRejected 必須作為函數(shù)被調(diào)用
2.2.6 then方法可能被多次調(diào)用
- 2.2.6.1 如果promise變成了 fulfilled態(tài),所有的onFulfilled回調(diào)都需要按照then的順序執(zhí)行
- 2.2.6.2 如果promise變成了 rejected態(tài),所有的onRejected回調(diào)都需要按照then的順序執(zhí)行
2.2.7 then必須返回一個(gè)promise
- promise2 = promise1.then(onFulfilled, onRejected);
- 2.2.7.1 onFulfilled 或 onRejected 執(zhí)行的結(jié)果為x,調(diào)用 resolvePromise
- 2.2.7.2 如果 onFulfilled 或者 onRejected 執(zhí)行時(shí)拋出異常e,promise2需要被reject
- 2.2.7.3 如果 onFulfilled 不是一個(gè)函數(shù),promise2 以promise1的值fulfilled
- 2.2.7.4 如果 onRejected 不是一個(gè)函數(shù),promise2 以promise1的reason rejected
2.3 resolvePromise
resolvePromise(promise2, x, resolve, reject)
2.3.1 如果 promise2 和 x 相等,那么 reject promise with a TypeError
2.3.2 如果 x 是一個(gè) promsie
- 2.3.2.1 如果x是pending態(tài),那么promise必須要在pending,直到 x 變成 fulfilled or rejected.
- 2.3.2.2 如果 x 被 fulfilled, fulfill promise with the same value.
- 2.3.2.3 如果 x 被 rejected, reject promise with the same reason.
2.3.3 如果 x 是一個(gè) object 或者 是一個(gè) function
- 2.3.3.1 let then = x.then.
- 2.3.3.2 如果 x.then 這步出錯(cuò),那么 reject promise with e as the reason..
- 2.3.3.3 如果 then 是一個(gè)函數(shù),then.call(x, resolvePromiseFn, rejectPromise)
- 2.3.3.3.1 resolvePromiseFn 的 入?yún)⑹?nbsp;y, 執(zhí)行 resolvePromise(promise2, y, resolve, reject);
- 2.3.3.3.2 rejectPromise 的 入?yún)⑹?nbsp;r, reject promise with r.
- 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都調(diào)用了,那么第一個(gè)調(diào)用優(yōu)先,后面的調(diào)用忽略。
- 2.3.3.3.4 如果調(diào)用then拋出異常e
- 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已經(jīng)被調(diào)用,那么忽略
- 2.3.3.3.4.3 否則,reject promise with e as the reason
- 2.3.3.4 如果 then 不是一個(gè)function. fulfill promise with x.
2.3.4 如果 x 不是一個(gè) object 或者 function,fulfill promise with x.
Promise的其他方法
雖然上述的promise源碼已經(jīng)符合PromiseA+的規(guī)范,但是原生的Promise還提供了一些其他方法,如:
- Promise.resolve()
- Promise.reject()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.all()
- Promise.race()
下面具體說(shuō)一下每個(gè)方法的實(shí)現(xiàn):
Promise.resolve
Promise.resolve(value) 返回一個(gè)以給定值解析后的Promise 對(duì)象.
- 如果 value 是個(gè) thenable 對(duì)象,返回的promise會(huì)“跟隨”這個(gè)thenable的對(duì)象,采用它的最終狀態(tài)
- 如果傳入的value本身就是promise對(duì)象,那么Promise.resolve將不做任何修改、原封不動(dòng)地返回這個(gè)promise對(duì)象。
- 其他情況,直接返回以該值為成功狀態(tài)的promise對(duì)象。
- Promise.resolve = function (param) {
- if (param instanceof Promise) {
- return param;
- }
- return new Promise((resolve, reject) => {
- if (param && typeof param === 'object' && typeof param.then === 'function') {
- setTimeout(() => {
- param.then(resolve, reject);
- });
- } else {
- resolve(param);
- }
- });
- }
thenable對(duì)象的執(zhí)行加 setTimeout的原因是根據(jù)原生Promise對(duì)象執(zhí)行的結(jié)果推斷的,如下的測(cè)試代碼,原生的執(zhí)行結(jié)果為: 20 400 30;為了同樣的執(zhí)行順序,增加了setTimeout延時(shí)。
測(cè)試代碼:
- let p = Promise.resolve(20);
- p.then((data) => {
- console.log(data);
- });
- let p2 = Promise.resolve({
- then: function(resolve, reject) {
- resolve(30);
- }
- });
- p2.then((data)=> {
- console.log(data)
- });
- let p3 = Promise.resolve(new Promise((resolve, reject) => {
- resolve(400)
- }));
- p3.then((data) => {
- console.log(data)
- });
Promise.reject
Promise.reject方法和Promise.resolve不同,Promise.reject()方法的參數(shù),會(huì)原封不動(dòng)地作為reject的理由,變成后續(xù)方法的參數(shù)。
- Promise.reject = function (reason) {
- return new Promise((resolve, reject) => {
- reject(reason);
- });
- }
Promise.prototype.catch
Promise.prototype.catch 用于指定出錯(cuò)時(shí)的回調(diào),是特殊的then方法,catch之后,可以繼續(xù) .then
- Promise.prototype.catch = function (onRejected) {
- return this.then(null, onRejected);
- }
Promise.prototype.finally
不管成功還是失敗,都會(huì)走到finally中,并且finally之后,還可以繼續(xù)then。并且會(huì)將值原封不動(dòng)的傳遞給后面的then.
- Promise.prototype.finally = function (callback) {
- return this.then((value) => {
- return Promise.resolve(callback()).then(() => {
- return value;
- });
- }, (err) => {
- return Promise.resolve(callback()).then(() => {
- throw err;
- });
- });
- }
Promise.all
Promise.all(promises) 返回一個(gè)promise對(duì)象
- 如果傳入的參數(shù)是一個(gè)空的可迭代對(duì)象,那么此promise對(duì)象回調(diào)完成(resolve),只有此情況,是同步執(zhí)行的,其它都是異步返回的。
- 如果傳入的參數(shù)不包含任何 promise,則返回一個(gè)異步完成.
- promises 中所有的promise都promise都“完成”時(shí)或參數(shù)中不包含 promise 時(shí)回調(diào)完成。
- 如果參數(shù)中有一個(gè)promise失敗,那么Promise.all返回的promise對(duì)象失敗
- 在任何情況下,Promise.all 返回的 promise 的完成狀態(tài)的結(jié)果都是一個(gè)數(shù)組
- Promise.all = function (promises) {
- promises = Array.from(promises);//將可迭代對(duì)象轉(zhuǎn)換為數(shù)組
- return new Promise((resolve, reject) => {
- let index = 0;
- let result = [];
- if (promises.length === 0) {
- resolve(result);
- } else {
- function processValue(i, data) {
- result[i] = data;
- if (++index === promises.length) {
- resolve(result);
- }
- }
- for (let i = 0; i < promises.length; i++) {
- //promises[i] 可能是普通值
- Promise.resolve(promises[i]).then((data) => {
- processValue(i, data);
- }, (err) => {
- reject(err);
- return;
- });
- }
- }
- });
- }
測(cè)試代碼:
- var promise1 = new Promise((resolve, reject) => {
- resolve(3);
- })
- var promise2 = 42;
- var promise3 = new Promise(function(resolve, reject) {
- setTimeout(resolve, 100, 'foo');
- });
- Promise.all([promise1, promise2, promise3]).then(function(values) {
- console.log(values); //[3, 42, 'foo']
- },(err)=>{
- console.log(err)
- });
- var p = Promise.all([]); // will be immediately resolved
- var p2 = Promise.all([1337, "hi"]); // non-promise values will be ignored, but the evaluation will be done asynchronously
- console.log(p);
- console.log(p2)
- setTimeout(function(){
- console.log('the stack is now empty');
- console.log(p2);
- });
Promise.race
Promise.race函數(shù)返回一個(gè) Promise,它將與第一個(gè)傳遞的 promise 相同的完成方式被完成。它可以是完成( resolves),也可以是失?。╮ejects),這要取決于第一個(gè)完成的方式是兩個(gè)中的哪個(gè)。
如果傳的參數(shù)數(shù)組是空,則返回的 promise 將永遠(yuǎn)等待。
如果迭代包含一個(gè)或多個(gè)非承諾值和/或已解決/拒絕的承諾,則 Promise.race 將解析為迭代中找到的第一個(gè)值。
- Promise.race = function (promises) {
- promises = Array.from(promises);//將可迭代對(duì)象轉(zhuǎn)換為數(shù)組
- return new Promise((resolve, reject) => {
- if (promises.length === 0) {
- return;
- } else {
- for (let i = 0; i < promises.length; i++) {
- Promise.resolve(promises[i]).then((data) => {
- resolve(data);
- return;
- }, (err) => {
- reject(err);
- return;
- });
- }
- }
- });
- }
測(cè)試代碼:
- Promise.race([
- new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
- undefined,
- new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
- ]).then((data) => {
- console.log('success ', data);
- }, (err) => {
- console.log('err ',err);
- });
- Promise.race([
- new Promise((resolve, reject) => { setTimeout(() => { resolve(100) }, 1000) }),
- new Promise((resolve, reject) => { setTimeout(() => { resolve(200) }, 200) }),
- new Promise((resolve, reject) => { setTimeout(() => { reject(100) }, 100) })
- ]).then((data) => {
- console.log(data);
- }, (err) => {
- console.log(err);
- });