Promise 學習筆記五 :: 拆解 Promise 面試題

文章目錄
- 開始前準備
- 小試身手
- (一)、第一題 (基本的 Promise 語法理解)
- (二)、第二題 (基本 Promise 的執行順序)
- (三)、第三題 (Promise resolve 的影響順序)
- (四)、第四題 (未解決 Promise 的行為)
- (五)、第五題 (函數返回 Promise 的順序)
- (六)、第六題 (多個 microTask 順序理解)
- (七)、第七題 (microTask 與 macroTask 執行優先順序)
- (八)、第八題 (包含 setTimeout 的 Promise 處理)
- (九)、第九題 (多個 setTimeout 和 microTask 組合處理)
- (十)、第十題 (混合 microTask 與 macroTask 的順序)
- 深度探索
從Promise第一篇開始到現在對於Promise有一定程度的理解了,還需驗證一下自己對於Promise的運作是否有盲區,本篇整理了一些面試題並由簡單到困難來一一拆解他。
開始前準備
對於ECMAScript的部分補充幾點。
[[Resolve]](promise, x),若x是thenable的處理
在上一篇手寫Promise中,對於x是thenable的處理邏輯是:
if(this.isPromiseLike(result)){
result.then(resolve, reject)
}
但在ECMAScript文件中在處理then時會在將邏輯送到事件隊列中

也可以從v8原始碼中看到

所以在瀏覽器中執行時等同以下程式碼:
if(this.isPromiseLike(result)){
queueMicrotask(() => {
result.then(resolve, reject);
})
}
拆解promise async/await語法糖
async/await語法糖對應的邏輯可以更好地理解運作方式。
async function test(){
console.log(1);
const value = await 2;
console.log(value);
}
//相當於是
function test(){
console.log(1);
return new Promise((resolve, rejecte) => {
resolve(2);
})
.then((value) => {
console.log(value);
});
}
小試身手
第一題 (基本的 Promise 語法理解)
console.log(1);
const p1 = new Promise((resolve, reject) => {
console.log(2);
});
console.log(3);
解題思路:
- 同步程式的執行順序是由上到下依序執行
- Promise建構函式的閉包函式是馬上執行的
從這個題目來看都是同步程式,故輸出的順序是
輸出結果
1
2
3
第二題 (基本 Promise 的執行順序)
console.log(1);
const p1 = new Promise((resolve, reject) => {
console.log(2);
resolve(3);
});
p1.then(value => {
console.log(value);
});
console.log(4);
解題思路:
- then函式內的onFulfilled會送到事件隊列中執行
輸出結果
1
2
4
3
第三題 (Promise resolve 的影響順序)
console.log(1);
const p1 = new Promise((resolve, reject) => {
console.log(2);
resolve(3);
console.log(4);
});
p1.then(value => {
console.log(value);
});
console.log(5);
解題思路:
- then函式內的onFulfilled會送到事件隊列中執行
輸出結果
1
2
4
5
3
第四題 (未解決 Promise 的行為)
console.log(1);
const p1 = new Promise((resolve, reject) => {
console.log(2);
});
p1.then(value => {
console.log(3);
});
console.log(4);
解題邏輯:
- 因為p1未被解決或拒絕所以then函式不會被執行
輸出結果
1
2
4
第五題 (函數返回 Promise 的順序)
console.log(1);
const func = () => (new Promise((resolve, reject) => {
console.log(2);
resolve(3);
}));
console.log(4);
func().then(value => {
console.log(value);
});
console.log(5);
解題邏輯:
- func函式返回值是一個promise
輸出結果
1
4
2
5
3
第六題 (多個 microTask 順序理解)
console.log(1);
Promise.resolve(2).then((value) => {
console.log(value);
});
Promise.resolve(3).then((value) => {
console.log(value);
});
console.log(4);
解題邏輯:
- then函式內的onFulfilled會送到microTask中執行
輸出結果
1
4
2
3
第七題 (microTask 與 macroTask 執行優先順序)
console.log(1);
setTimeout(() => {
console.log(2);
});
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
解題邏輯:
- setTimeout是macroTask
- microTask的執行優先權會大於macroTask
輸出結果
1
4
3
2
第八題 (包含 setTimeout 的 Promise 處理)
const p1 = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log(2);
resolve(3);
console.log(4);
}, 0);
console.log(5);
});
p1.then((value) => {
console.log(value);
});
console.log(6);
解題邏輯:
- microTask的執行優先權會大於macroTask
- 此題的microTask是在macroTask執行過程中才產生
輸出結果
1
5
6
2
4
3
第九題 (多個 setTimeout 和 microTask 組合處理)
const t1 = setTimeout(() => {
console.log(1);
const p1 = Promise.resolve().then(() => {
console.log(2);
});
}, 0);
const t2 = setTimeout(() => {
console.log(3);
}, 0);
解題邏輯:
- microTask的執行優先權會大於macroTask
解題過程:
- 一開始先產生兩個macroTask事件以下簡稱macro1, macro2
- 當macro1執行過程中會產生microTask事件簡稱micro1
- 此時macro1執行完成後會先執行micro1才會執行macro2
輸出結果
1
2
3
第十題 (混合 microTask 與 macroTask 的順序)
console.log(1);
const p1 = Promise.resolve().then(() => {
console.log(2);
const t1 = setTimeout(() => {
console.log(3);
}, 0);
});
const t2 = setTimeout(() => {
console.log(4);
const p2 = Promise.resolve().then(() => {
console.log(5);
});
}, 0);
console.log(6);
解題邏輯:
- microTask的執行優先權會大於macroTask
解題過程:
- 一開始先產生一個microTask (micro1)一個macroTask (macro1)事件
- micro1執行過程產生macroTask (macro2)
- macro1執行過程產生microTask (micro2)
- microTask優先執行所以先執行micro2之後才執行macro2
輸出結果
1
6
2
4
5
3
深度探索
接下來的題型會比較複雜一下會在解題過程中適當加入microTask及mcaroTask的示意。
第十一題 (多層 thenable 結構理解)
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((value) => {
console.log(value);
});
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
});
解題邏輯:
- 在本篇開頭"開始前準備"有提到ECMAScript中的then會加到microTask中執行
解題過程:
- 開始將兩個事件加入到microTask中
microTask 1. console.log(0); return Promise.resolve(4); 2. console.log(1);
- 輸出 0,將thenable 加入 microTask
microTask 1. console.log(1); 2. thenable Promise.resolve(4);
- 輸出 1,將 console.log(2) 加入 microTask
microTask 1. thenable Promise.resolve(4); 2. console.log(2);
- 無輸出,將thenable then函式 加入microTask
microTask 1. console.log(2); 2. Promise.resolve(4).then();
- 輸出 2,將 console.log(3) 加入 microTask
microTask 1. Promise.resolve(4).then(); 2. console.log(3);
- 無輸出,執行thenable then函式將 console.log(value) 加入microTask
microTask 1. console.log(3); 2. console.log(value); // 4
- 輸出 3,將 console.log(5) 加入 microTask
microTask 1. console.log(value); // 4 2. console.log(5);
- 輸出 4,將 console.log(6) 加入 microTask
microTask 1. console.log(5); 2. console.log(6);
- 輸出 5
microTask 1. console.log(6);
- 輸出 6
輸出結果
0
1
2
3
4
5
6
第十二題 (自定義 thenable 行為)
Promise.resolve().then(() => {
console.log(0);
return {then(resolve){resolve(4)}};
}).then((res) => {
console.log(res);
});
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
});
解題邏輯:
- 與上一題邏輯相似需要額外注意的是,返回的thenable執行then時並不會加入到microTask中
輸出結果
0 1 2 4 3 5 6
第十三題 (多層 await 的 Promise 順序分析)
async function m1() {
return 1;
}
async function m2() {
const n = await m1();
console.log(n);
return 2;
}
async function m3() {
const n = m2();
console.log(n);
return 3;
}
m3()
.then((n) => {
console.log(n);
});
m3();
console.log(4);
解題邏輯:
- 有加上asnyc關鍵字的函式返回的會是promise
- await 後方的可以視為then執行且會加入microTask
解題過程:
- 執行m3函式,m3呼叫m2但沒加await不會持續等待並直接返回pending狀態的promise,故輸出 Promise{<Pending>}
- 執行m2函式呼叫m1且有await會持續等待m1,但m1是立即完成所以將1放入microTask中
- m3有執行then函式,將console.log(n) 放入microTask中
- 執行m3函式,m3呼叫m2但沒加await不會持續等待並直接返回pending狀態的promise,故輸出 Promise{<Pending>}
- 執行m2函式呼叫m1且有await會持續等待m1,但m1是立即完成所以將1放入microTask中
- 執行console.log(4) 輸出 4
- 此時的microTask如下
1. console.log(n); // 1 return 2; 2. console.log(n); // 3 3. console.log(n); // 1 return 2;
- 執行microTask中第一個console.log(n) 輸出 1 並 return 2,但因為m2沒有await所以不處理
- 執行microTask中第二個console.log(n) 輸出 3
- 執行microTask中第一個console.log(n) 輸出 1 並 return 2,但因為m2沒有await所以不處理
輸出結果
Promise{<Pending>}
Promise{<Pending>}
4
1
3
1
第十四題 (未賦值的變數和microTask, marcoTask順序)
var a;
var b = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
resolve();
}, 1000);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(4);
});
a = new Promise(async(resolve, reject) => {
console.log(a);
await b;
console.log(a);
console.log(5);
await a;
resolve(true);
console.log(6);
});
console.log(7);
解題邏輯:
- 聲明變數未賦值則是 undefined
- microTask及macroTask執行優先順序
- 變數b的promise是鏈式執行最後一個返回值的promise
解題過程:
- 輸出 1,resolve加入到macroTask一秒後解決,此時promise為pending狀態,待解決後才將console.log(2)送到microTask
- 變數b的promise是console.log(4)這個then promise
- 輸出undefined 因為現在變數a尚未賦值,且等待b完成,此時完成賦值Promise{<Pending>}
- 輸出 7
- 目前microTask及macroTask如下
microTask macroTask 1. resolve();
- 一秒後執行resolve,將 console.log(2) 加入microTask
microTask 1. console.log(2); macroTask
- 輸出 2,將 console.log(3) 加入microTask
microTask 1. console.log(3); macroTask
- 輸出 3,將 console.log(4) 加入microTask
microTask 1. console.log(4); macroTask
- 輸出 4,此時b promise已完成,回到await b的地方繼續
- 輸出 Promise{<Pending>}
- 輸出 5
- await a 因為 resolve在後方所以根本等不到
輸出結果
1
7
undefined
2
3
4
Promise{<Pending>}
5
第十五題 (多個 Promise 組合錯誤處理)
const p1 = Promise.resolve(1);
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 1000);
});
const p3 = Promise.reject(3);
function runPromise(promise) {
return promise
.then((value) => {
console.log(value);
return value;
})
.catch((reason) => {
console.error(`Error: `, reason)
return 'Error'
});
}
async function runPromises() {
try{
const result1 = await runPromise(p1);
console.log(`log1: `, result1);
const result2 = await runPromise(p2);
console.log(`log2: `, result2);
const result3 = await runPromise(p3);
console.log(`log3: `, result3);
}catch(error){
console.error(`Ohoh: `, error);
}
}
runPromises();
解題邏輯:
- microTask及macroTask執行優先順序
- await 後方的可以視為then執行且會加入microTask
解題過程:
- p2 resolve加入到macroTask一秒後解決
microTask macroTask 1. resolve(2);
- 執行runPromises,將p1給runPromise,因p1已解決所以將console.log(value)加入到microTask
microTask 1. console.log(value); // 1 return value; macroTask 1. resolve(2);
- microTask執行優先大於macroTask
- 輸出 1
microTask macroTask 1. resolve(2);
- 輸出 log1: 1
- 執行runPromise(p2),p2仍處於pending狀態持續等待
- 執行resolve(2),將console.log(value) 加入到 microTask
microTask 1. console.log(value); // 2 return value; macroTask
- 輸出 2
- 輸出 log2: 2
- 執行runPromise(p3),因p3已拒絕所以將console.error(`Error: `, reason)加入到microTask
microTask 1. console.error(`Error: `, reason); // 3 return 'Error'; macroTask
- 輸出Error: 3
- 輸出log3: Error
輸出結果
1
log1: 1
2
log2: 2
Error: 3
log3: Error