從標(biāo)準(zhǔn)實現(xiàn)入手理解 Promise
為什么寫這篇
網(wǎng)上解釋已經(jīng)一抓一大把,但是個人覺得大部分文章可以實現(xiàn) Promise/A+,但是對真實細(xì)節(jié)沒完全實現(xiàn):
- 真正的 microtask(大部分用的 setTimeout 代替)
- then 傳入回調(diào)函數(shù)返回 Promise 對象的情況
- 構(gòu)造函數(shù) executor 的細(xì)節(jié),以及 executor 的 resolve 參數(shù)傳入 Promise 對象的情況
- Promise.resolve 傳入 Promise 對象的情況
- Promise 各種靜態(tài)函數(shù)傳入的是可迭代對象而非數(shù)組
- 介紹清楚循環(huán)調(diào)用阻止的情況和多次 resolve 阻止的情況
本人用自己淺薄的知識嘗試去覆蓋最真實的 Promise 所有實現(xiàn)情況,并且用純 js 實現(xiàn),從中理解到真正的 Promise 是怎么樣的。
什么是 Promise
Promise 是異步編程的一種解決方案,比傳統(tǒng)的解決方案回調(diào)函數(shù)和事件更合理和更強大。它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對象。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結(jié)束的事件(通常是一個異步操作)的結(jié)果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統(tǒng)一的 API,各種異步操作都可以用同樣的方法進行處理,并非只能解決讀取接口這種異步操作。
在此之前如何解決異步操作
在 Promise 出現(xiàn)之前其實也有出現(xiàn)了類 Promise 的庫,例如Q和JQueryDeferred 等。但是通用的解決方案就是回調(diào)函數(shù)。
回調(diào)函數(shù)本身是沒問題的,但是如果需要一個接一個調(diào)用(嵌套)時,很容易掉入回調(diào)地獄
ajax({
url: './index',
success: function (value) {
ajax({
url: value.url,
success: function (value2) {
ajax({
url: value2.url,
success: function (value3) {
ajax({
url: value3.url,
success: function (value4) {
// .. do something else
},
});
},
});
},
});
},
});
在有 Promise 后就可以寫成:
const request = url =>
new Promise((resolve, reject) => {
ajax({
url,
success: resolve,
fail: reject,
});
});
request('.index')
.then(({ url }) => request(url))
.then(({ url }) => request(url))
.then(({ url }) => request(url))
.then(value4 => {
// .. do something else
});
甚至在配合 es2017 中的 async 函數(shù)可以寫成:
(async () => {
const { url } = await request('.index');
const { url: url2 } = await request(url);
const { url: url3 } = await request(url2);
const value4 = await request(url3);
// .. do something else
})();
整個代碼就清晰易懂而且優(yōu)雅簡潔。
Promise 特點
Promise對象有以下兩個特點。
(1)對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):pending(進行中)、fulfilled(已成功)和 rejected(已失?。?。只有異步操作的結(jié)果,可以決定當(dāng)前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變。
(2)一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結(jié)果。Promise對象的狀態(tài)改變,只有兩種可能:從 pending 變?yōu)?fulfilled 和從 pending 變?yōu)?rejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結(jié)果,這時就稱為 resolved(已定型)。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結(jié)果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結(jié)果的。
Promise 的 api 和用法
這里就不詳細(xì)列舉了,詳細(xì)可以參考 es6 入門中的Promise 章。
下面會默認(rèn)讀者已清楚了解 Promise 的 api。
在開始前...
是否能說清楚下面打印的是什么,而且說出思路么?
new Promise(res => {
res();
console.log(1);
// 代碼塊1
})
.then(() => {
// 代碼塊2
console.log(2);
new Promise(res => {
res();
console.log(3);
})
.then(() => {
// 代碼塊3
console.log(4);
new Promise(res => {
res();
console.log(5);
}).then(() => {
// 代碼塊4
console.log(8);
});
})
.then(() => {
// 代碼塊5
console.log(9);
new Promise(res => {
res();
console.log(10);
}).then(() => {
// 代碼塊6
console.log(12);
});
});
Promise.resolve()
.then(() => {
// 代碼塊7
console.log(6);
})
.then(() => {
// 代碼塊8
console.log(11);
});
})
.then(() => {
// 代碼塊9
console.log(7);
});
Promise.reject(
new Promise((res, rej) => {
setTimeout(() => {
rej(133222);
res(444);
}, 2000);
})
)
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs))
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
Promise.resolve(
new Promise((res, rej) => {
setTimeout(() => {
rej(133222);
res(444);
}, 2000);
})
)
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs))
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
new Promise((res, rej) => {
res(Promise.reject(123));
})
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
new Promise((res, rej) => {
rej(Promise.resolve(123));
})
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
new Promise(res => res(Promise.resolve())).then(() => console.log(2));
Promise.resolve(Promise.resolve()).then(() => console.log(1));
Promise.resolve()
.then(() => {
console.log(1);
return Promise.resolve(5);
})
.then(r => {
console.log(r);
});
Promise.resolve()
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(6);
});
可以自己嘗試思考再去控制臺嘗試,如果回答不上就應(yīng)該往下看啦
Promise 規(guī)范
關(guān)于 Promise 的規(guī)范最早是由 commonjs 社區(qū)提出,畢竟多人接收的就是Promise/A,后面因規(guī)范較為簡單所以在這基礎(chǔ)上提出了Promise/A+,這也是業(yè)界和 ES6 使用的標(biāo)準(zhǔn),而 ES6 在這標(biāo)準(zhǔn)上還新增了 Promise.resolve、Promise.reject、Promise.all、Promise.race、Promise.prototype.catch、Promise.allSettled、Promise.prototype.finally 等方法。
而測試是否符合 Promise/A+標(biāo)準(zhǔn)的可以使用promises-aplus-tests庫來測試,使用方法為在自己實現(xiàn)的 MyPromise 文件中加入如下代碼導(dǎo)出
// MyPromise.js
MyPromise.defer = MyPromise.deferred = function () {
let dfd = {};
dfd.promise = new MyPromise((resolve, reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
});
return dfd;
};
module.exports = MyPromise;
然后可以安裝并在文件目錄內(nèi)運行
npm install -g promises-aplus-tests
promises-aplus-tests MyPromise.js
微任務(wù)創(chuàng)建器
我們都知道 Promise 的 then 創(chuàng)建的是異步任務(wù)(microtask),而我們需要實現(xiàn) Promise 的話當(dāng)然不能用他來創(chuàng)建,網(wǎng)上各種實現(xiàn)可能基本都是 setTimeout,這個是將任務(wù)推入下一宏任務(wù)(macrotask)中。所以我們需要一個微任務(wù)創(chuàng)建器,這時候就需要用到幾個 api 了:一個是queueMicrotask,還有MutationObserver,這兩個都是可以將回調(diào)函數(shù)推入微任務(wù)隊列的,然后我們可以先封裝一個降級方案:
// 判斷是否函數(shù)
const isFunction = target => typeof target === 'function';
// 判斷是否原生方法
const isNativeFunction = Ctor =>
isFunction(Ctor) && /native code/.test(Ctor.toString());
// 推入微任務(wù)隊列
const nextTaskQueue = cb => {
if (
typeof queueMicrotask !== 'undefined' &&
isNativeFunction(queueMicrotask)
) {
queueMicrotask(cb);
} else if (
typeof MutationObserver !== 'undefined' &&
(isNativeFunction(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]')
) {
const observer = new MutationObserver(cb);
const node = document.createTextNode('1');
observer.observe(node, {
characterData: true,
});
node.data = '2';
} else if (typeof process !== 'undefined' && isFunction(process.nextTick)) {
process.nextTick(cb);
} else {
setTimeout(() => {
cb();
}, 0);
}
};
如此,后面我們需要創(chuàng)建異步 tick 就可以使用這個nextTaskQueue。
Promise 結(jié)構(gòu)
可以從日常使用 Promise 中了解到,Promise 需要 new 出實例,并且傳入回調(diào)函數(shù),而回調(diào)函數(shù)接收兩個參數(shù)(resolve、reject),回調(diào)函數(shù)會立刻執(zhí)行。返回的 Promise 實例中可調(diào)用 then 或者 catch 接收完成和錯誤。
Promise 擁有三種狀態(tài)分別為pending等待中、fulfilled已完成、rejected已拒絕,并且初始為等待中,且如果更改了狀態(tài)則無法再更改。
而Promise.prototype.then可以接收兩個參數(shù),分別是onFulfilled和onRejected回調(diào)函數(shù),Promise.prototype.catch只能接收onRejected。
const STATUS = {
PENDING: 'PENDING', // 等待中
FULFILLED: 'FULFILLED', // 已完成
REJECTED: 'REJECTED', // 已拒絕
};
class MyPromise {
status = STATUS.PENDING; // 初始化狀態(tài)為等待中
constructor(executor) {
const resolve = value => {};
const reject = reason => {};
try {
executor(resolve, reject); // 實例化即刻執(zhí)行
} catch (err) {
reject(err); // 發(fā)生錯誤則被捕捉
}
}
then(onFulfiled, onRejected) {}
}
我們發(fā)現(xiàn) Promise 對象在每次使用 then 或者 catch 后獲取的值都會一致不變,而且在完成前多個 then 或者 catch 監(jiān)聽會在完成、拒絕后一個個調(diào)用,所以知道這里會保存值和錯誤以及維護一個完成和拒絕的隊列
const STATUS = {
PENDING: 'PENDING', // 等待中
FULFILLED: 'FULFILLED', // 已完成
REJECTED: 'REJECTED', // 已拒絕
};
class MyPromise {
_resolveQueue = []; // 完成回調(diào)隊列
_rejectQueue = []; // 拒絕回調(diào)隊列
result = void 0; // 完成的值
state = STATUS.PENDING; // 初始化狀態(tài)為等待中
constructor(executor) {
const resolve = value => {
// 只有在等待中的狀態(tài)才可以resolve
if (this.state === STATUS.PENDING) {
try {
// 如果傳入resolve內(nèi)的為thenable對象,則以它的狀態(tài)為準(zhǔn)
resolvePromise(this, value, realResolve, reject);
} catch (err) {
reject(err);
}
}
};
const realResolve = value => {
// 只有在等待中的狀態(tài)才可以resolve
this.state = STATUS.FULFILLED; // 修改狀態(tài)
this.result = value; // 保存值
// 真正的創(chuàng)建了微任務(wù)的封裝
nextTaskQueue(() => {
while (this._resolveQueue.length) {
const callback = this._resolveQueue.shift();
callback(value); // 一個個執(zhí)行
}
});
};
const reject = reason => {
// 與resolve一致,只是修改的狀態(tài)和保存的理由以及執(zhí)行的隊列不一樣
if (this.state === STATUS.PENDING) {
this.state = STATUS.REJECTED;
this.result = reason;
nextTaskQueue(() => {
while (this._rejectQueue.length) {
const callback = this._rejectQueue.shift(); // 獲取拒絕回調(diào)隊列
callback(reason);
}
});
}
};
try {
executor(resolve, reject); // 實例化即刻執(zhí)行
} catch (err) {
reject(err); // 發(fā)生錯誤則被捕捉
}
}
then(onFulfiled, onRejected) {}
}
到這里,構(gòu)造函數(shù)已經(jīng)差不多了,剩下的開始實現(xiàn)then方法。
我們知道,then 后面可以鏈?zhǔn)秸{(diào)用 then,并且then 獲取的值為上一個 then 返回的新 Promise 對象中的值,很多人誤認(rèn)為鏈?zhǔn)秸{(diào)用獲取的是鏈?zhǔn)降念^的 Promise,其實不然,Promise 每個 then 都會創(chuàng)建一個新 Promise,所以你下一個 then 跟最前面的 Promise 不一定有關(guān)系。
而且,如果 then 中傳入的不是函數(shù),則會直接傳出,直到被傳入函數(shù)的 then 捕捉。
然后,在調(diào)用 then 時,Promise 對象可能為三種狀態(tài),但是即使是已完成或已拒絕,也不會立刻執(zhí)行,而是被推入微任務(wù)隊列中。
class MyPromise {
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value; // 如果不是函數(shù)則傳遞給下一個
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
return new MyPromise((resolve, reject) => {
if (this.state === STATUS.FULFILLED) {
nextTaskQueue(() => {
// 即使Promise對象是已完成,也不會立刻執(zhí)行
const result = onFulfilled(this.result); // 傳入的回調(diào)可以獲取值
resolve(result);
});
} else if (this.state === STATUS.REJECTED) {
nextTaskQueue(() => {
// 即使Promise對象是已拒絕,也不會立刻執(zhí)行
const result = onRejected(this.result); // 傳入的回調(diào)可以拒絕理由
reject(result);
});
} else if (this.state === STATUS.PENDING) {
// 如果是等待中,則分別推入回調(diào)隊列中
this._resolveQueue.push(() => {
const result = onFulfilled(this.result);
resolve(result);
});
this._rejectQueue.push(() => {
const result = onRejected(this.result);
reject(result);
});
}
});
}
}
到這里為止,基本差不多了,然而并沒有這么簡單?;卣{(diào)函數(shù)可能是任何值,包括返回了一個 Promise 對象,這種情況需要以返回的 Promise 為準(zhǔn)。并且如果是這種情況,后面 then 會被延遲兩個 tick 執(zhí)行,具體實現(xiàn)可以參考 V8 引擎對ResolvePromise和PromiseResolveThenableJob的實現(xiàn),這里不作深入講解。
所以這里可以封裝出一個方法專門處理 Promise 以及其回調(diào),以適配所有標(biāo)準(zhǔn)
const resolvePromise = (newPromise, result, resolve, reject) => {
/**
* 規(guī)范2.3.1,避免循環(huán)引用
* e.g. const p = MyPromise.resolve().then(() => p);
*/
if (newPromise === result) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
/**
* 用來判斷resolvePormise是否已經(jīng)執(zhí)行過了,如果執(zhí)行過resolve或者reject就不要再往下走resolve或者reject
* 在一些返回thenable對象中,連續(xù)調(diào)用多次回調(diào)的情況
* e.g. then(() => {
* return {
* then(resolve){
* resolve(1);
* resolve(2);
* }
* }
* })
* 網(wǎng)上大部分的都沒說這個情況到底是什么
*/
let called = false;
if (result !== null && (typeof result === 'object' || isFunction(result))) {
try {
const { then } = result;
if (isFunction(then)) {
// 規(guī)范2.3.3.3 如果result是個thenable對象,則調(diào)用其then方法,當(dāng)他是Promise
then.call(
result,
value => {
if (!called) {
called = true;
// 現(xiàn)代瀏覽器中,如果then返回是thenable對象則會延遲一次執(zhí)行,而本身的then又會延遲,所以其實是兩次
nextTaskQueue(() => {
resolvePromise(newPromise, value, resolve, reject); // 這里需要遞歸取值,直到不是Promise為止
});
}
},
reason => {
if (!called) {
called = true;
nextTaskQueue(() => {
reject(reason);
});
}
}
);
} else {
// 規(guī)范2.3.3.4 如果 result不是thenable對象,則返回fulfilled
resolve(result);
}
} catch (err) {
if (!called) {
called = true;
reject(err);
}
}
} else {
resolve(result);
}
};
所以 then 方法改為
then(onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === 'function' ? onFulfilled : value => value; // 如果不是函數(shù)則傳遞給下一個
onRejected =
typeof onRejected === 'function'
? onRejected
: reason => {
throw reason;
};
let newPromise;
return (newPromise = new MyPromise((resolve, reject) => {
if (this.state === STATUS.FULFILLED) {
nextTaskQueue(() => {
// 即使Promise對象是已完成,也不會立刻執(zhí)行
try {
const result = onFulfilled(this.result); // 傳入的回調(diào)可以獲取值
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
} else if (this.state === STATUS.REJECTED) {
nextTaskQueue(() => {
// 即使Promise對象是已拒絕,也不會立刻執(zhí)行
try {
const result = onRejected(this.result); // 傳入的回調(diào)可以拒絕理由
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
} else if (this.state === STATUS.PENDING) {
// 如果是等待中,則分別推入回調(diào)隊列中
this._resolveQueue.push(() => {
try {
const result = onFulfilled(this.result);
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
this._rejectQueue.push(() => {
try {
const result = onRejected(this.result);
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
}
}));
}
最后加入上面說的測試導(dǎo)出,跑一次測試即可
[圖片上傳失敗...(image-67c4ae-1621751597335)]
827 個測試項全通過~!
剩下的可以增加一些 api 實現(xiàn)和判斷即可。
但是要注意的是,靜態(tài)方法包括 all、race、allSettled、any 等,傳入的是任意可迭代對象,包括字符串等,如果傳入的迭代對象中的子元素如果非 Promise 對象,則直接返回,而即使是非 Promise 對象,也是需要推入微任務(wù)在下一 tick 執(zhí)行(很多實現(xiàn)忽略了這些)。
tips
因為個人在網(wǎng)上看了很多類似的,但是并沒有很完整的解釋細(xì)節(jié),例如 called 是做什么的。
所以自己總結(jié)了一下。
因為發(fā)現(xiàn)很多同學(xué)覺得 Promise 就是用來封裝讀接口的通訊方法的,這里表示 Promise不僅僅可以做讀接口封裝,還可以做很多有趣的封裝
例如:
- wait 等待幾秒后執(zhí)行
const wait = time =>
new Promise(resolve => {
const timer = setTimeout(() => resolve(timer), time);
});
(async () => {
console.log(1);
await wait(2000);
console.log(2);
})();
// print 1
// wait for 2 seconds
// print 2
- 早期的小程序 api promise 化,因為本人 16 年開始接觸小程序,那時候小程序全是 success 和 fail 回調(diào),用起來很頭疼(現(xiàn)在全支持 thenable 調(diào)用了),所以做了個 promisify 函數(shù)。
const promisify =
wxapi =>
(options, ...args) =>
new Promise((resolve, reject) =>
wxapi.apply(null, [
{
...options,
success: resolve,
fail: err => {
console.log(err);
reject(err);
},
},
...args,
])
);
(async () => {
await promisify(wx.login)();
await promisify(wx.checkSession)();
// session有效!
})();
const loading = (title = '加載中..') => {
promisify(wx.showLoading)({
title: i18n.getLocaleByName(title),
mask: true,
});
};
最后
在了解了源碼后,其實可以延伸出一些 Promise 執(zhí)行順序的問題
new Promise(res => {
res();
console.log(1);
// 代碼塊1
})
.then(() => {
// 代碼塊2
console.log(2);
new Promise(res => {
res();
console.log(3);
})
.then(() => {
// 代碼塊3
console.log(4);
new Promise(res => {
res();
console.log(5);
}).then(() => {
// 代碼塊4
console.log(8);
});
})
.then(() => {
// 代碼塊5
console.log(9);
new Promise(res => {
res();
console.log(10);
}).then(() => {
// 代碼塊6
console.log(12);
});
});
Promise.resolve()
.then(() => {
// 代碼塊7
console.log(6);
})
.then(() => {
// 代碼塊8
console.log(11);
});
})
.then(() => {
// 代碼塊9
console.log(7);
});
以上可以解釋一下執(zhí)行順序
- tick1、代碼塊 1先執(zhí)行,resolve 了,將代碼塊 2推入 nextTick,
打印1 - tick2、代碼塊 2執(zhí)行,
打印2,創(chuàng)建 Promise,resolve 了所以將代碼塊 3推入 nextTick,打印3;往下走,Promise.resolve 創(chuàng)建了一個 fulfilled 的 Promise,所以代碼塊 7推入 nextTick,執(zhí)行完畢代碼塊 2,所以代碼塊 9被推入 nextTick - tick3、代碼塊 3執(zhí)行,
打印4,創(chuàng)建 Promise,resolve 了將代碼塊 4推入 nextTick,打印5,執(zhí)行完 then 所以將下一個 then 的代碼塊 5推入 nextTick;然后代碼塊 7執(zhí)行,打印6,執(zhí)行完所以將下一個 then 的代碼塊 8推入 nextTick;執(zhí)行代碼塊 9,打印7 - tick4、代碼塊 4執(zhí)行,
打印8;代碼塊 5執(zhí)行,打印9,創(chuàng)建新 Promise,resolve 了所以將代碼塊 6推入 nextTick,打印10;代碼塊 8執(zhí)行,打印11 - tick5、代碼塊 6執(zhí)行,
打印12
Promise.reject(
new Promise((res, rej) => {
setTimeout(() => {
rej(133222);
res(444);
}, 2000);
})
)
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs))
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
這里很容易理解,會立刻執(zhí)行第二個 catch 和第三個 then,打印 pending 中的 Promise 對象和 undefined,因為 Promise.reject 會創(chuàng)建一個已 rejected 的 Promise 對象,value 為傳入的值。
Promise.resolve(
new Promise((res, rej) => {
setTimeout(() => {
rej(133222);
res(444);
}, 2000);
})
)
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs))
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
但是這里就不一樣了,會等待 2 秒后,但是還是第二個 catch 和第三個 then。因為 Promise.resolve 如果傳入的是 thenable 對象,則返回以此為準(zhǔn)。
new Promise((res, rej) => {
res(Promise.reject(123));
})
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
這里會以為調(diào)用 then,因為調(diào)用了內(nèi)部的 resolve 方法,其實不然,這里會走 catch 回調(diào)并且打印 catch 和 123,因為 resolve 內(nèi)如果傳入 thenable 對象則會一次為準(zhǔn)
new Promise((res, rej) => {
rej(Promise.resolve(123));
})
.then(rs => console.log('then', rs))
.catch(rs => console.log('catch', rs));
而 reject 則不會。
new Promise(res => res(Promise.resolve())).then(() => console.log(2));
Promise.resolve(Promise.resolve()).then(() => console.log(1));
Promise.resolve()
.then(() => {
console.log(1);
return Promise.resolve(5);
})
.then(r => {
console.log(r);
});
Promise.resolve()
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(6);
});
Promise.resolve()
.then(() => {
console.log(1);
return {
then(r) {
r(5);
},
};
})
.then(r => {
console.log(r);
});
Promise.resolve()
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
})
.then(() => {
console.log(6);
});
這兩個可以一塊說,大部分網(wǎng)上的例子都沒實現(xiàn)這塊的邏輯,也是 Promise 的一個需要注意的細(xì)節(jié):就是 resolve、then 傳入的回調(diào)函數(shù)的返回,如果是 Promise 對象,則會延遲兩個 tick。
為什么呢,這塊當(dāng)然涉及到 v8 實現(xiàn)的源碼,不說這么復(fù)雜簡單化來說的話就是,Promise 會先把 then 執(zhí)行一次,這里會有一個 tick(如果是 thenable 對象則不會,因為并非原生的then),執(zhí)行這個 then 時傳入的回調(diào)會包含另一個 tick 的延遲。
完整實現(xiàn)
下面貼出全代碼實現(xiàn),包含了
- Promise/A+規(guī)范實現(xiàn)
- 所有目前(ES2021)的 Promise 實例方法(finally)、靜態(tài)方法(any、allSettled)等的實現(xiàn)
- 將異步任務(wù)正確推入微任務(wù)隊列
- then 傳入的回調(diào)函數(shù)返回 Promise 對象,延遲兩個 tick
- 構(gòu)造函數(shù)的參數(shù) executor 中 resolve 入?yún)魅霝?Promise 對象也會延遲兩個 tick
- Promise.resolve 傳入 Promise 對象時會直接將其返回出去,Promise.reject 則不然
- Promise 各種靜態(tài)方法(all、race、any、allsettled)傳入的是可迭代對象而非數(shù)組
- Promise 各種靜態(tài)方法(all、race、any、allsettled)傳入的可迭代對象成員如果不是 Promise 對象會直接返回,但是也是會進入下一微任務(wù)(很多實現(xiàn)都是直接 resolve 并沒有延遲)