Promise 是 JavaScript 中用于處理異步操作的對象,它代表了一個可能還沒有完成的操作的最終完成或失敗,以及其結果值。Promise 對象有三種狀態:
- Pending(進行中):初始狀態,既不是成功,也不是失敗狀態。
- Fulfilled(已成功):操作成功完成。
- Rejected(已失敗):操作失敗。
一個 Promise 對象一旦從 Pending 狀態轉變為 Fulfilled 或 Rejected,它的狀態就不會再改變。
Promise 的核心原理包括:
- 構造函數:通過
new Promise(executor)
創建 Promise 對象,其中executor
是一個帶有兩個參數的函數,通常稱為resolve
和reject
,它們分別用于將 Promise 狀態改為 Fulfilled 和 Rejected。- 狀態管理:Promise 對象的狀態只能從 Pending 變為 Fulfilled 或從 Pending 變為 Rejected,一旦狀態改變,就不能再變。
- 鏈式調用:通過
.then()
和.catch()
方法可以鏈式調用 Promise,處理異步操作的結果或錯誤。- 異步執行:Promise 的
executor
函數是立即執行的,但其resolve
和reject
函數是異步調用的。- 錯誤處理:通過
.catch()
方法或.then(null, onRejected)
可以捕獲鏈式調用中的錯誤。
你真的完全了解promise嗎,請輸出以下代碼題的結果
const promise = new Promise((resolve, reject) => {console.log(1);console.log(2);
});
promise.then(() => {console.log(3);
});
console.log(4);
const promise1 = new Promise((resolve, reject) => {console.log('promise1')resolve('resolve1')
})
const promise2 = promise1.then(res => {console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
Promise.resolve().then(() => {console.log('promise1');const timer2 = setTimeout(() => {console.log('timer2')}, 0)
});
const timer1 = setTimeout(() => {console.log('timer1')Promise.resolve().then(() => {console.log('promise2')})
}, 0)
console.log('start');
?答案:
?如果你回答的和答案不一致,請務必接收下面的Promise解題指南
?Promise必備能力
Promise 是 JavaScript 中處理異步操作的一種機制,它代表了一個異步操作的最終完成或失敗,并可以獲取其結果。以下是 Promise 的關鍵知識點,掌握這些內容將有助于應對 Promise 相關的筆試題:
1. Promise 的基本概念
- 狀態(State):一個 Promise 有三種狀態:
pending
(進行中)、fulfilled
(已成功)和rejected
(已失敗)。- 結果(Result):Promise 的狀態一旦從
pending
變為fulfilled
或rejected
,就不再改變,且會有一個不可變的終值或拒因。
2. Promise 的創建
- 使用
new Promise(executor)
構造函數創建 Promise,其中executor
是一個帶有兩個參數的函數:resolve
和reject
,分別用于將 Promise 的狀態變為fulfilled
和rejected
。
3. Promise 的方法
then(onFulfilled, onRejected)
:注冊成功和失敗時的回調函數,返回一個新的 Promise。catch(onRejected)
:注冊失敗時的回調函數,相當于.then(null, onRejected)
,返回一個新的 Promise。finally(onFinally)
:注冊無論成功或失敗都會執行的回調函數,返回一個新的 Promise。
4. Promise 鏈
.then
和.catch
方法返回的都是新的 Promise 實例,這允許鏈式調用,形成 Promise 鏈。- 鏈中的每個
.then
或.catch
都會返回一個新的 Promise,其狀態和結果取決于前一個 Promise 的狀態和回調函數的返回值。
5. Promise 的靜態方法
Promise.resolve(value)
:返回一個以給定值解析的 Promise 對象。Promise.reject(reason)
:返回一個帶有拒絕原因的 Promise 對象。Promise.all(iterable)
:返回一個 Promise,當 iterable 中所有 Promise 都 fulfilled 時,返回的 Promise 才會 fulfilled,其結果是一個包含所有 Promise 結果的數組。如果有一個 Promise 被 rejected,則返回的 Promise 立即 rejected,且結果為第一個被 rejected 的 Promise 的拒因。Promise.race(iterable)
:返回一個 Promise,一旦 iterable 中的某個 Promise fulfilled 或 rejected,返回的 Promise 就會采用相同的狀態和結果。
6. Promise 的錯誤處理
- 未處理的 Promise 拒絕會被
unhandledrejection
事件捕獲。- 使用
.catch
或.then(null, onRejected)
可以捕獲 Promise 鏈中的錯誤。
?Promise 的性能考慮:Promise 的創建和執行是異步的,不會阻塞主線程。避免不必要的 Promise 創建,因為每個 Promise 都會占用一定的內存。
?Promise 的局限性:Promise 一旦狀態改變,就無法再次改變。Promise 沒有提供取消異步操作的機制。
事件循環機制
事件循環(Event Loop)是 JavaScript 運行時環境中的一個重要概念,它與 Promise 的使用密切相關,因為 Promise 的回調是通過事件循環來執行的。理解事件循環機制有助于解決與異步編程、回調函數、Promise 和 async/await 等相關的問題。作為前端開發者,你必須要理解并掌握下面的內容:
- 理解事件循環的基本流程和宏任務、微任務的區別。
- 知道 Promise 的回調是如何通過事件循環執行的,以及為什么
.then
和.catch
的回調通常比setTimeout
更快執行。- 應用到實際問題中,比如理解為什么在
setTimeout
和Promise
的回調中,await
前的代碼會立即執行,而await
后的代碼則在 Promise 解決后執行。- 理解事件循環與異步編程(如 async/await)的關系,以及如何避免回調地獄(Callback Hell)。
1. 事件循環的工作原理
-
宏任務(Macrotask):主要包括
setTimeout
、setInterval
、I/O 操作
、UI 渲染
(如瀏覽器的重繪和重排) -
微任務(Microtask):主要包括
Promise 的 .then
和process.nextTick
中的回調。 -
執行棧(Execution Context Stack):用于執行同步代碼,每次執行棧空時,事件循環會從宏任務隊列中取出一個宏任務執行,然后執行所有微任務,再繼續取下一個宏任務。
-
事件循環的流程:
- 執行同步代碼直到執行棧為空。
- 從宏任務隊列中取出一個宏任務執行,執行完畢后執行所有微任務。
- 重復步驟 2 直到宏任務隊列和微任務隊列都空。
- 如果有新的微任務(如
process.nextTick
),將其添加到微任務隊列。
2. 與 Promise 的關系
- 當一個 Promise 被 resolve 或 reject 時,它會創建一個微任務,這個微任務會將回調函數添加到微任務隊列中,等待執行。
.then
和.catch
的回調會在微任務隊列中執行,這意味著它們會等待當前的宏任務和微任務執行完畢后才會運行。- 如果在回調中又創建了新的 Promise,會形成一個微任務鏈,直到微任務隊列為空
注意:宏任務是一次執行一個,微任務是一次清空一個隊列。兩者有本質區別
Promise的幾道基礎題
1. 題目一
const promise1 = new Promise((resolve, reject) => {console.log('promise1')
})
console.log('1', promise1);
思路:Promise本身是同步的。從上至下,先遇到new Promise,執行該構造函數中的代碼promise1。然后執行同步代碼1,此時promise1沒有被resolve或者reject,因此狀態還是pending
答案:
2 題目二
const promise = new Promise((resolve, reject) => {console.log(1);resolve('success')console.log(2);
});
promise.then(() => {console.log(3);
});
console.log(4);
思路:從上至下,先遇到new Promise,執行其中的同步代碼1
再遇到resolve('success'), 將promise的狀態改為了resolved并且將值保存下來
繼續執行同步代碼2
跳出promise,往下執行,碰到promise.then這個微任務,將其加入微任務隊列
執行同步代碼4
本輪宏任務全部執行完畢,檢查微任務隊列,發現promise.then這個微任務且狀態為resolved,執行它。
答案:
3 題目三
const promise = new Promise((resolve, reject) => {console.log(1);console.log(2);
});
promise.then(() => {console.log(3);
});
console.log(4);
?思路:和題目二相似,只不過在
promise
中并沒有resolve
或者reject。
因此promise.then并不會執行,它只有在被改變了狀態之后才會執行答案:
4 題目四
const promise1 = new Promise((resolve, reject) => {console.log('promise1')resolve('resolve1')
})
const promise2 = promise1.then(res => {console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
思路:從上至下,先遇到
new Promise
,執行該構造函數中的代碼promise1
碰到
resolve
函數, 將promise1
的狀態改變為resolved
, 并將結果保存下來碰到
promise1.then
這個微任務,將它放入微任務隊列
promise2
是一個新的狀態為pending的
Promise`執行同步代碼1, 同時打印出
promise1
的狀態是resolved
執行同步代碼2,同時打印出
promise2
的狀態是pending
宏任務執行完畢,查找微任務隊列,發現
promise1.then
這個微任務且狀態為resolved,執行它。
5 題目五
const fn = () => (new Promise((resolve, reject) => {console.log(1);resolve('success')
}))
fn().then(res => {console.log(res)
})
console.log('start')
?思路:fn函數它是直接返回了一個new Promise的,而且fn函數的調用是在start之前,所以它里面的內容應該會先執行。
答案:
6 題目六
如果把fn的調用放到start之后呢?
const fn = () =>new Promise((resolve, reject) => {console.log(1);resolve("success");});
console.log("start");
fn().then(res => {console.log(res);
});
思路:fn是一個函數,只有在函數調用的時候才會執行。因此先執行console.log("start"),再處理fn,之后是then方法
答案:
Promise結合setTimeout
Promise通常會結合事件循環機制進行考察異步輸出結果
1 題目一?
console.log('start')
setTimeout(() => {console.log('time')
})
Promise.resolve().then(() => {console.log('resolve')
})
console.log('end')
思路:剛開始整個腳本作為一個宏任務來執行,對于同步代碼直接壓入執行棧進行執行,因此先打印出start和end。
setTimout作為一個宏任務被放入宏任務隊列(下一個)
Promise.then作為一個微任務被放入微任務隊列
本次宏任務執行完,檢查微任務,發現Promise.then,執行它
接下來進入下一個宏任務,發現setTimeout,執行。
答案:
2. 題目二
const promise = new Promise((resolve, reject) => {console.log(1);setTimeout(() => {console.log("timerStart");resolve("success");console.log("timerEnd");}, 0);console.log(2);
});
promise.then((res) => {console.log(res);
});
console.log(4);
思路:從上至下,先遇到new Promise,執行該構造函數中的代碼1
然后碰到了定時器,將這個定時器中的函數放到下一個宏任務的延遲隊列中等待執行 執行同步代碼2
跳出promise函數,遇到promise.then,但其狀態還是為pending,這里理解為先不執行 執行同步代碼4
一輪循環過后,進入第二次宏任務,發現延遲隊列中有setTimeout定時器,執行它
首先執行timerStart,然后遇到了resolve,將promise的狀態改為resolved且保存結果并將之前的promise.then推入微任務隊列
繼續執行同步代碼timerEnd
宏任務全部執行完畢,查找微任務隊列,發現promise.then這個微任務,執行它
答案:
3 題目三
題目三分了兩個題目,因為看著都差不多,不過執行的結果卻不一樣,大家不妨先猜猜下面兩個題目分別執行什么:
setTimeout(() => {console.log('timer1');setTimeout(() => {console.log('timer3')}, 0)
}, 0)
setTimeout(() => {console.log('timer2')
}, 0)
console.log('start')
setTimeout(() => {console.log('timer1');Promise.resolve().then(() => {console.log('promise')})
}, 0)
setTimeout(() => {console.log('timer2')
}, 0)
console.log('start')
思路:分析代碼,還是先執行同步代碼;然后將setTimeout放入宏任務隊列中。對于第一個題,同步執行完,宏任務隊列執行,輸出timer1,此時又遇到一個setTimeout,繼續加入隊列,并從隊頭取出timer2并輸出。
對于第二個代碼:先執行同步代碼,輸出start;然后執行setTimeout,輸出timer1,遇到微任務promise.resolve.then,此時promise的狀態也是resolve,因此可以執行then。并且then是微任務,timer1之后后執行微任務,輸出promise。
答案:
第一個輸出結果
第二個輸出結果
?4.題目四
Promise.resolve().then(() => {console.log('promise1');const timer2 = setTimeout(() => {console.log('timer2')}, 0)
});
const timer1 = setTimeout(() => {console.log('timer1')Promise.resolve().then(() => {console.log('promise2')})
}, 0)
console.log('start');
思路:剛開始整個腳本作為第一次宏任務來執行,我們將它標記為宏1,從上至下執行。
遇到Promise.resolve().then這個微任務,將then中的內容加入第一次的微任務隊列標記為微1
遇到定時器timer1,將它加入下一次宏任務的延遲列表,標記為宏2,等待執行(先不管里面是什么內容)
執行宏1中的同步代碼start
第一次宏任務(宏1)執行完畢,檢查第一次的微任務隊列(微1),發現有一個promise.then這個微任務需要執行
執行打印出微1中同步代碼promise1,然后發現定時器timer2,將它加入宏2的后面,標記為宏3
第一次微任務隊列(微1)執行完畢,執行第二次宏任務(宏2),首先執行同步代碼timer1
然后遇到了promise2這個微任務,將它加入此次循環的微任務隊列,標記為微2
宏2中沒有同步代碼可執行了,查找本次循環的微任務隊列(微2),發現了promise2,執行它
第二輪執行完畢,執行宏3,打印出timer2
5.題目五
const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('success')}, 1000)
})
const promise2 = promise1.then(() => {throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {console.log('promise1', promise1)console.log('promise2', promise2)
}, 2000)
思路:從上至下,先執行第一個new Promise中的函數,碰到setTimeout將它加入下一個宏任務列表。跳出new Promise,碰到promise1.then這個微任務,但其狀態還是為pending,這里理解為先不執行。
- promise2是一個新的狀態為pending的Promise
- 執行同步代碼console.log('promise1'),且打印出的promise1的狀態為pending
- 執行同步代碼console.log('promise2'),且打印出的promise2的狀態為pending
- 碰到第二個定時器,將其放入下一個宏任務列表
- 第一輪宏任務執行結束,并且沒有微任務需要執行,因此執行第二輪宏任務
- 先執行第一個定時器里的內容,將promise1的狀態改為resolved且保存結果并將之前的promise1.then推入微任務隊列
- 該定時器中沒有其它的同步代碼可執行,因此執行本輪的微任務隊列,也就是promise1.then,它拋出了一個錯誤,且將promise2的狀態設置為了rejected
- 第一個定時器執行完畢,開始執行第二個定時器中的內容
- 打印出'promise1',且此時promise1的狀態為resolved
- 打印出'promise2',且此時promise2的狀態為rejected
'promise1' Promise{<pending>} 'promise2' Promise{<pending>} test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102 'promise1' Promise{<resolved>: "success"} 'promise2' Promise{<rejected>: Error: error!!!}
6.題目六
如果你上面這道題搞懂了之后,我們就可以來做做這道了,你應該能很快就給出答案:
const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve("success");console.log("timer1");}, 1000);console.log("promise1里的內容");
});
const promise2 = promise1.then(() => {throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {console.log("timer2");console.log("promise1", promise1);console.log("promise2", promise2);
}, 2000);
思路:promise的then方法一定要在promise本身執行并且resolve的時候才會執行
答案:
'promise1里的內容' 'promise1' Promise{<pending>} 'promise2' Promise{<pending>} 'timer1' test5.html:102 Uncaught (in promise) Error: error!!! at test.html:102 'timer2' 'promise1' Promise{<resolved>: "success"} 'promise2' Promise{<rejected>: Error: error!!!}
Promise中的then、catch、finally
- Promise 的狀態一經改變(resolve 或 reject),就不能再改變。
.then
和.catch
返回一個新的 Promise。.catch
能捕獲上層的錯誤,無論被連接到哪里。- 在 Promise 中,返回任意一個非 Promise 的值都會被包裹成 Promise 對象。
- 如果 Promise 內部的狀態一經改變,并且有了一個值,那么后續每次調用
.then
或.catch
都會直接拿到該值。- 在
.then
或.catch
中返回一個 error 對象并不會拋出錯誤,不會被后續的.catch
捕獲。.then
或.catch
返回的值不能是 Promise 本身,否則會造成死循環。.then
或.catch
的參數期望是函數,傳入非函數會發生值穿透。.then
方法可以接收兩個參數,第一個是處理成功的函數,第二個是處理失敗的函數。.catch
可以認為是.then
第二個參數的簡便寫法。.finally
方法也是返回一個 Promise,在 Promise 結束時(resolved 或 rejected)都會執行里面的回調函數。
1 題目一?
const promise = new Promise((resolve, reject) => {resolve("success1");reject("error");resolve("success2");
});
promise
.then(res => {console.log("then: ", res);}).catch(err => {console.log("catch: ", err);})
思路:考察promise的狀態之后被更改一次
答案:
"then: success1"
2 題目二
const promise = new Promise((resolve, reject) => {reject("error");resolve("success2");
});
promise.then((res) => {console.log("then1: ", res);}).then((res) => {console.log("then2: ", res);}).catch((err) => {console.log("catch: ", err);}).then((res) => {console.log("then3: ", res);});
?思路:先被reject改變狀態。因此promise不會走then方法,而是被catch捕獲。catch不管放在哪個位置都能捕獲到error。同時catch默認返回一個新的promise,通過then方法輸出then3
答案:
3 題目三
Promise.resolve(1).then(res => {console.log(res);return 2;}).catch(err => {return 3;}).then(res => {console.log(res);});
?思路:Promise通過.resolve手動更改Promise狀態,因此執行then方法。return 2會被包裝成resolve(2)
4.題目四
const promise = new Promise((resolve, reject) => {setTimeout(() => {console.log('timer')resolve('success')}, 1000)
})
const start = Date.now();
promise.then(res => {console.log(res, Date.now() - start)
})
promise.then(res => {console.log(res, Date.now() - start)
})
? 思路:Promise被執行一次,但Promise的then和catch可以被執行多次
答案:
5 題目五
Promise.resolve().then(() => {return new Error('error!!!')
}).then(res => {console.log("then: ", res)
}).catch(err => {console.log("catch: ", err)
})
?思路:通過new Error不會拋出錯誤,而是一個Promise包裝的對象。這里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))
- 你可能想到的是進入.catch然后被捕獲了錯誤。
- 結果并不是這樣的,它走的是.then里面:
答案:
6. 題目六
Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log)
思路:.then 或者 .catch 的參數期望是函數,傳入非函數則會發生值穿透。
第一個then和第二個then中傳入的都不是函數,一個是數字類型,一個是對象類型,因此發生了穿透,將resolve(1) 的值直接傳到最后一個then里。
7.題目七
下面來介紹一下.then函數中的兩個參數。
第一個參數是用來處理Promise成功的函數,第二個則是處理失敗的函數。 也就是說Promise.resolve('1')的值會進入成功的函數,Promise.reject('2')的值會進入失敗的函數。
讓我們來看看這個例子
Promise.reject('err!!!').then((res) => {console.log('success', res)}, (err) => {console.log('error', err)}).catch(err => {console.log('catch', err)})
思路:then方法的第二個參數接收一個回調,如果Promise失敗,執行then的第二個參數。這里的執行結果是:'error' 'error!!!'
而如果把第二個參數去掉,就進入了catch()中:
Promise.reject('err!!!').then((res) => {console.log('success', res)}).catch(err => {console.log('catch', err)})
執行結果:
'catch' 'error!!!'
但是有一個問題,如果是這個案例呢?
Promise.resolve().then(function success (res) {throw new Error('error!!!')}, function fail1 (err) {console.log('fail1', err)}).catch(function fail2 (err) {console.log('fail2', err)})
思路:如果是then里面的方法拋出錯誤會被外面的catch住,而不會走then的第二個參數。由于Promise調用的是resolve(),因此.then()執行的應該是success()函數,可是success()函數拋出的是一個錯誤,它會被后面的catch()給捕獲到,而不是被fail1函數捕獲。
因此執行結果為:
fail2 Error: error!!!at success
8.題目八
接著來看看.finally(),這個功能一般不太用在面試中,不過如果碰到了你也應該知道該如何處理。
function promise1 () {let p = new Promise((resolve) => {console.log('promise1');resolve('1')})return p;
}
function promise2 () {return new Promise((resolve, reject) => {reject('error')})
}
promise1().then(res => console.log(res)).catch(err => console.log(err)).finally(() => console.log('finally1'))promise2().then(res => console.log(res)).catch(err => console.log(err)).finally(() => console.log('finally2'))
思路:finally方法不管promise是什么狀態都會執行'promise1' '1' 'error' 'finally1' 'finally2'
?Promise中的all和race
- 在做下面的題目之前,讓我們先來了解一下Promise.all()和Promise.race()的用法。
- 通俗來說,.all()的作用是接收一組異步任務,然后并行執行異步任務,并且在所有異步操作執行完后才執行回調。
.race()
的作用也是接收一組異步任務,然后并行執行異步任務,只保留取第一個執行完成的異步操作的結果,其他的方法仍在執行,不過執行結果會被拋棄。
1. 題目一
function runAsync(x) {const p = new Promise((resolve) =>setTimeout(() => {console.log(x);resolve(x);}, 1000));return p;
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
?思路:并行執行多個異步操作,并且在一個回調中處理所有的返回數據。
- all()后面的.then()里的回調函數接收的就是所有異步操作的結果。
- 而且這個結果中數組的順序和Promise.all()接收到的數組順序一致
2 題目二
新增一個runReject函數,它用來在1000 * x秒后reject一個錯誤。
function runAsync(x) {const p = new Promise((resolve) =>setTimeout(() => {console.log("runAsync", x);resolve(x);}, 1000));return p;
}
function runReject(x) {const p = new Promise((resolve, reject) =>setTimeout(() => {console.log("runReject", x);reject(`Error: ${x}`);}, 1000 * x));return p;
}Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)]).then((res) => console.log(".all成功結果", res)).catch((err) => console.log(".all失敗結果", err));
?思路:
Promise.all
接收一個 Promise 數組,這里包含兩個runAsync
和兩個runReject
的調用。Promise.all
會等待所有 Promise 全部成功解決后返回一個結果數組,如果其中任何一個 Promise 拒絕,它會立即返回拒絕的 Promise 的結果。具體執行過程如下:
runAsync(1)
和runAsync(3)
同時啟動,因為它們的setTimeout
都設置為1秒,所以它們會在1秒后打印 "runAsync 1" 和 "runAsync 3",然后各自解決并返回1
和3
。runReject(4)
和runReject(2)
同時啟動,它們的setTimeout
根據傳入的參數設置為4秒和2秒。runReject(4)
在4秒后拒絕,打印 "runReject 4" 和 "Error: 4",返回一個拒絕的 Promise。runReject(2)
在2秒后拒絕,打印 "runReject 2" 和 "Error: 2",同樣返回一個拒絕的 Promise。.catch是會捕獲最先的那個異常,在這道題目中最先的異常就是runReject(2)的結果答案:
3 題目三
將上一題的Promise.all改成Promise.race
function runAsync(x) {const p = new Promise((resolve) =>setTimeout(() => {console.log("runAsync", x);resolve(x);}, 1000));return p;
}
function runReject(x) {const p = new Promise((resolve, reject) =>setTimeout(() => {console.log("runReject", x);reject(`Error: ${x}`);}, 1000 * x));return p;
}Promise.race([runAsync(1), runReject(4), runAsync(3), runReject(2)]).then((res) => console.log(".all成功結果", res)).catch((err) => console.log(".all失敗結果", err));
思路:使用.race()方法,它只會獲取最先執行完成的那個結果,其它的異步任務雖然也會繼續進行下去,不過race已經不管那些任務的結果了 。Promise.race的then方法只會返回第一個成功的Promise返回結果。
?如果第一個成功的是reject狀態呢
Promise.race([runReject(0),runAsync(1),runReject(4),runAsync(3),runReject(2),
]).then((res) => console.log(".all成功結果", res)).catch((err) => console.log(".all失敗結果", err));
那么將返回失敗的結果,也就是Promise.race后的catch方法
async/await的幾道題
既然談到了Promise,那就肯定得再說說async/await,在很多時候async和Promise的解法差不多,又有些不一樣。不信你來看看題目一。
1. 題目一
async function async1() {console.log("async1 start");await async2();console.log("async1 end");
}
async function async2() {console.log("async2");
}
async1();
console.log('start')
思路:
- 首先一進來是創建了兩個函數的,我們先不看函數的創建位置,而是看它的調用位置 發現async1函數被調用了,然后去看看調用的內容
- 執行函數中的同步代碼async1 start,之后碰到了await,它會阻塞async1后面代碼的執行,因此會先去執行async2中的同步代碼async2,然后跳出async1
- 跳出async1函數后,執行同步代碼start
- 在一輪宏任務全部執行完之后,再來執行剛剛await后面的內容async1 end。
答案:
?
2. 題目二
async function async1() {console.log("async1 start");await async2();console.log("async1 end");
}
async function async2() {setTimeout(() => {console.log('timer')}, 0)console.log("async2");
}
async1();
console.log("start")
思路:
這段代碼中,首先執行?
async1()
?函數。在?async1()
?函數中,遇到?await async2()
?時會暫停?async1()
?函數的執行,等待?async2()
?函數執行完畢。然而,在?async2()
?函數中,雖然有一個?setTimeout
,但是它是一個宏任務,會被放入事件隊列中等待執行,而不會阻塞當前的微任務執行。因此,執行順序如下:
- 執行?
async1()
?函數,打印出 "async1 start"。- 遇到?
await async2()
,暫停?async1()
?函數的執行,轉而執行?async2()
?函數。- 在?
async2()
?函數中,首先打印出 "async2",然后設置了一個 0 秒后執行的?setTimeout
,但是這個?setTimeout
?是一個宏任務,會被放入事件隊列中等待執行。- 繼續執行主程序,打印出 "start"。
- 主程序執行完畢后,開始執行微任務隊列中的任務,繼續執行?
async1()
?函數中的剩余部分,打印出 "async1 end"。- 最后,事件隊列中的宏任務執行,執行?
setTimeout
?中的回調函數,打印出 "timer"。
3.題目三
async function async1() {console.log("async1 start");await async2();console.log("async1 end");setTimeout(() => {console.log('timer1')}, 0)
}
async function async2() {setTimeout(() => {console.log('timer2')}, 0)console.log("async2");
}
async1();
setTimeout(() => {console.log('timer3')
}, 0)
console.log("start")
其實如果你能做到這里了,說明你前面的那些知識點也都掌握了,我就不需要太過詳細的步驟分析了。注意:await后面是微任務,打印start后,先去async1里將await后面的微任務執行完
4.題目四
async function async1 () {console.log('async1 start');await new Promise(resolve => {console.log('promise1')})console.log('async1 success');return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
思路:在async1中await后面的Promise是沒有返回值的,也就是它的狀態始終是pending狀態,因此相當于一直在await,await,await卻始終沒有響應...所以在await之后的內容是不會執行的,也包括async1后面的 .then。
5.題目五
在上題基礎上給Promise加上resolve
async function async1 () {console.log('async1 start');await new Promise(resolve => {console.log('promise1')resolve('promise1 resolve')}).then(res => console.log(res))console.log('async1 success');return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
?