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

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

從Promise第一篇開始到現在對於Promise有一定程度的理解了,還需驗證一下自己對於Promise的運作是否有盲區,本篇整理了一些面試題並由簡單到困難來一一拆解他。

對於ECMAScript的部分補充幾點。

在上一篇手寫Promise中,對於x是thenable的處理邏輯是:

if(this.isPromiseLike(result)){
	result.then(resolve, reject)
}

但在ECMAScript文件中在處理then時會在將邏輯送到事件隊列中

截圖 2024-10-28 晚上9.04.57.png

也可以從v8原始碼中看到 

截圖 2024-10-28 晚上9.05.02.png

所以在瀏覽器中執行時等同以下程式碼:

if(this.isPromiseLike(result)){
	queueMicrotask(() => {
		result.then(resolve, reject);
	})
}

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);
		});
}
console.log(1);
const p1 = new Promise((resolve, reject) => {
	console.log(2);
});
console.log(3);

解題思路:

  • 同步程式的執行順序是由上到下依序執行
  • Promise建構函式的閉包函式是馬上執行的

從這個題目來看都是同步程式,故輸出的順序是

輸出結果
1
2
3
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
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
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
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
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
console.log(1);

setTimeout(() => {
	console.log(2);
});

Promise.resolve().then(() => {
	console.log(3);
});

console.log(4);

解題邏輯:

  • setTimeout是macroTask
  • microTask的執行優先權會大於macroTask
輸出結果
1
4
3
2
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
const t1 = setTimeout(() => {
	console.log(1);

	const p1 = Promise.resolve().then(() => {
		console.log(2);
	});
}, 0);

const t2 = setTimeout(() => {
	console.log(3);
}, 0);

解題邏輯:

  • microTask的執行優先權會大於macroTask

解題過程:

  1. 一開始先產生兩個macroTask事件以下簡稱macro1, macro2
  2. 當macro1執行過程中會產生microTask事件簡稱micro1
  3. 此時macro1執行完成後會先執行micro1才會執行macro2
輸出結果
1
2
3
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

解題過程:

  1. 一開始先產生一個microTask (micro1)一個macroTask (macro1)事件
  2. micro1執行過程產生macroTask (macro2)
  3. macro1執行過程產生microTask (micro2)
  4. microTask優先執行所以先執行micro2之後才執行macro2
輸出結果
1
6
2
4
5
3

接下來的題型會比較複雜一下會在解題過程中適當加入microTask及mcaroTask的示意。

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中執行

解題過程:

  1. 開始將兩個事件加入到microTask中
  2. microTask
    1.
        console.log(0);
        return Promise.resolve(4);
    2.
        console.log(1);
  3. 輸出 0,將thenable 加入 microTask
  4. microTask
    1.
    	console.log(1);
    2.
    	thenable Promise.resolve(4);
  5. 輸出 1,將 console.log(2) 加入 microTask
  6. microTask
    1.
    	thenable Promise.resolve(4);
    2.
    	console.log(2);
  7. 無輸出,將thenable then函式 加入microTask
  8. microTask
    1.
    	console.log(2);
    2.
    	Promise.resolve(4).then();
  9. 輸出 2,將 console.log(3) 加入 microTask
  10. microTask
    1.
    	Promise.resolve(4).then();
    2.
    	console.log(3);
  11. 無輸出,執行thenable then函式將 console.log(value) 加入microTask
  12. microTask
    1.
    	console.log(3);
    2.
    	console.log(value); // 4
  13. 輸出 3,將 console.log(5) 加入 microTask
  14. microTask
    1.
    	console.log(value); // 4
    2.
    	console.log(5);
  15. 輸出 4,將 console.log(6) 加入 microTask
  16. microTask
    1.
    	console.log(5);
    2.
    	console.log(6);
  17. 輸出 5
  18. microTask
    1.
    	console.log(6);
  19. 輸出 6
輸出結果
0
1
2
3
4
5
6
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
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

解題過程:

  1. 執行m3函式,m3呼叫m2但沒加await不會持續等待並直接返回pending狀態的promise,故輸出 Promise{<Pending>}
  2. 執行m2函式呼叫m1且有await會持續等待m1,但m1是立即完成所以將1放入microTask中
  3. m3有執行then函式,將console.log(n) 放入microTask中
  4. 執行m3函式,m3呼叫m2但沒加await不會持續等待並直接返回pending狀態的promise,故輸出 Promise{<Pending>}
  5. 執行m2函式呼叫m1且有await會持續等待m1,但m1是立即完成所以將1放入microTask中
  6. 執行console.log(4) 輸出 4
  7. 此時的microTask如下
  8. 1.
    	console.log(n); // 1
    	return 2;
    2.
    	console.log(n); // 3
    3.
    	console.log(n); // 1
    	return 2;
  9. 執行microTask中第一個console.log(n) 輸出 1 並 return 2,但因為m2沒有await所以不處理
  10. 執行microTask中第二個console.log(n) 輸出 3
  11. 執行microTask中第一個console.log(n) 輸出 1 並 return 2,但因為m2沒有await所以不處理
輸出結果
Promise{<Pending>}
Promise{<Pending>}
4
1
3
1
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. 輸出 1,resolve加入到macroTask一秒後解決,此時promise為pending狀態,待解決後才將console.log(2)送到microTask
  2. 變數b的promise是console.log(4)這個then promise
  3. 輸出undefined 因為現在變數a尚未賦值,且等待b完成,此時完成賦值Promise{<Pending>}
  4. 輸出 7
  5. 目前microTask及macroTask如下
  6. microTask
    	
    	
    macroTask
    1.
    	resolve();
  7. 一秒後執行resolve,將 console.log(2) 加入microTask
  8. microTask
    1.
    	console.log(2);
    	
    macroTask
    
  9. 輸出 2,將 console.log(3) 加入microTask
  10. microTask
    1.
    	console.log(3);
    	
    macroTask
    
  11. 輸出 3,將 console.log(4) 加入microTask
  12. microTask
    1.
    	console.log(4);
    	
    macroTask
    
  13. 輸出 4,此時b promise已完成,回到await b的地方繼續
  14. 輸出 Promise{<Pending>}
  15. 輸出 5
  16. await a 因為 resolve在後方所以根本等不到
輸出結果
1
7
undefined
2
3
4
Promise{<Pending>}
5
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

解題過程:

  1. p2 resolve加入到macroTask一秒後解決
  2. microTask
    	
    macroTask
    1.
    	resolve(2);
  3. 執行runPromises,將p1給runPromise,因p1已解決所以將console.log(value)加入到microTask
  4. microTask
    1.
    	console.log(value); // 1
    	return value;
    macroTask
    1.
    	resolve(2);
  5. microTask執行優先大於macroTask
  6. 輸出 1
  7. microTask
    	
    macroTask
    1.
    	resolve(2);
  8. 輸出 log1: 1
  9. 執行runPromise(p2),p2仍處於pending狀態持續等待
  10. 執行resolve(2),將console.log(value) 加入到 microTask
  11. microTask
    1.
    	console.log(value); // 2
    	return value;
    	
    macroTask
    
  12. 輸出 2
  13. 輸出 log2: 2
  14. 執行runPromise(p3),因p3已拒絕所以將console.error(`Error: `, reason)加入到microTask
  15. microTask
    1.
    	console.error(`Error: `, reason); // 3
    	return 'Error';
    	
    macroTask
    
  16. 輸出Error: 3
  17. 輸出log3: Error
輸出結果
1
log1: 1
2
log2: 2
Error: 3
log3: Error
作者頭像
Nick

是一位專業的網站開發者,不止擅長前端技術,也精通後端技術。