JS裡的異步構造函數
衆所周知,Js的構造函數是不能加上async/await來實作異步執行個體化的,一般當需要一個對象的屬性是異步的結果時可以這樣寫:
//! 一個需要指定時間後傳回的異步函數
function delay(timeout) {
return new Promise((resolve) => setTimeout(() => resolve("end"), timeout));
}
class Test {
async init() {
this.end = await delay(1000);
}
}
(async function () {
const test = new Test(); //? 執行個體化
await test.init(); //? 初始化end屬性
console.log(test.end);
})();
但是當我想要在執行個體化時就調用該屬性時就還要調用一次init(),這未免太麻煩了,是以想要在執行個體化時就把該屬性指派,就像這樣
const test = await new Test()
。
這時找到了這篇文章,該作者提供了這樣一段代碼來實作了異步構造函數:
class MyClass {
constructor(timeout) {
this.completed = false
const init = (async () => {
await delay(timeout)
this.completed = true
delete this.then
return this
})()
this.then = init.then.bind(init)
}
}
(async function(){
const myclass = await new MyClass(1000);
})()
在解釋這段代碼前就得說說PromiseLike了:一個有then方法,且該方法接收resolve,reject兩個參數的對象,就像這樣:
const PromiseLike = {
then(resolve) {
resolve("i am PromiseLike");
},
};
(async function () {
const something = await PromiseLike;
console.log(something); // i am PromiseLike
})();
即使PromiseLike不是一個函數,它也會被await調用對象裡的then方法并resolve出結果
現在回到剛才那段代碼,注意到它最後的一段了嗎
this.then = init.then.bind(init)
這句話把一個異步函數
init
的
then
給了執行個體,是以在調用
new Myclass
後得到的執行個體上有着一個then方法,這個then方法又會被前面的
await
解析,這時實質上解析的就是
init
這個異步函數的
then
而這個
then
傳回的是該類的執行個體化删除
then
後的
this
這樣
await new MyClass()
會等到
init
的異步執行完畢才會傳回值,這個傳回值是
init
的resolve。
總結一下:該方法其實仍然是同步執行個體化出了對象,但是
await
會馬上異步執行執行個體化裡
then
,然後傳回出
then
裡删除了
then
方法的
this
,這樣就做到了一句話進行執行個體化并初始化異步屬性。
知道了這個原理後,最初的問題就解決了:
class Test {
constructor() {
const init = (async () => {
this.end = await delay(1000);
delete this.then;
return this;
})();
this.then = init.then.bind(init);
}
}
(async function () {
const test = await new Test();
console.log(test.end); // end
})();
該作者也提供了一個基類用來繼承出異步構造函數,可以到原文去看看。
參考:異步構造函數 - 構造函數與Promise的結合