深入探究JS中的異步編程和事件循環(huán)機(jī)制
異步編程

js是單線程事件循環(huán)模型,同步操作與異步操作時(shí)代碼所依賴的核心機(jī)制。異步行為是為了優(yōu)化因計(jì)算量大而時(shí)間長(zhǎng)的操作。
同步:當(dāng)一個(gè)進(jìn)程在執(zhí)行某個(gè)請(qǐng)求時(shí),如果這個(gè)請(qǐng)求需要等待一段時(shí)間才能返回,那么這個(gè)進(jìn)程會(huì)一直等下去,直到請(qǐng)求返回。
異步:當(dāng)一個(gè)進(jìn)程在執(zhí)行某個(gè)請(qǐng)求時(shí),如果這個(gè)請(qǐng)求需要等待一段時(shí)間才能返回,那么這個(gè)進(jìn)程會(huì)直接向下執(zhí)行,不需要等待,當(dāng)消息返回時(shí)再通知進(jìn)程處理。
Promise
Promise的出現(xiàn)主要時(shí)解決之間異步編程使用回調(diào)函數(shù)的回調(diào)地獄的問(wèn)題。
Promise的狀態(tài)
Promise有三種狀態(tài):
- pending:待定
- fulfilled:成功
- rejected:失敗
兩種狀態(tài)變化:
- pending -> fulfilled:在promise中調(diào)用resolve()函數(shù)會(huì)產(chǎn)生此狀態(tài)轉(zhuǎn)變
- pending ->rejected:在promise中調(diào)用reject()函數(shù)會(huì)產(chǎn)生此狀態(tài)轉(zhuǎn)變
promise的基本流程
Promise構(gòu)造函數(shù)
用于創(chuàng)建Promise對(duì)象,需要傳入一個(gè)執(zhí)行器函數(shù),函數(shù)中需要帶兩個(gè)參數(shù):resolved,rejected;用于修改Promise的狀態(tài)
let p = new Promise((resolved,rejected)=>{
console.log(1);
resolved();
})
// 1
console.log(p); //Promise?{<fulfilled>: undefined}Promise.prototype.then()
用于定義成功或失敗狀態(tài)的回調(diào)函數(shù),并返回一個(gè)新的Promise。
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
resolved("ok");
},1000);
});
p.then(res=>{
console.log(res);
},err=>{
console.log(err);
})
// ok
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
rejected("err");
},1000);
});
p.then(res=>{
console.log(res);
},err=>{
console.log(err);
})
// errPromise.prototypr.catch()
用于定義失敗的回調(diào),then方法的語(yǔ)法糖,相當(dāng)于.then(undefined,onRejected);
let p = new Promise((resolved,rejected)=>{
setTimeout(()=>{
rejected("err");
},1000);
});
p.then(res=>{
console.log(res);
}).catch(err=>{
console.log(err);
})
// errPromise.resolve()
返回一個(gè)成功或者失敗的Promise;
let p = Promise.resolve("ok"); //Promise?{<fulfilled>: ok}
let p = Promise.resolve(Promise.reject('err')); //Promise?{<rejected>: 'err'}Promise.all()
返回一個(gè)新的Promise,只有所有的Promise都成功才成功,否則有一個(gè)失敗就返回失敗的promise
let p = Promise.all([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve()
]) // Promise?{<fulfilled>: [1,2,undefined]}
let p = Promise.all([
Promise.reject('err'),
Promise.resolve(1),
Promise.resolve(2)
]) // Promise?{<rejected>: 'err'}Promise.race()
返回一個(gè)新的Promise,第一個(gè)完成的Promise的結(jié)果狀態(tài)就是最終的結(jié)果狀態(tài)。
let p = Promise.race([
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve()
]) // Promise?{<fulfilled>: 1}
let p = Promise.all([
Promise.reject('err'),
Promise.resolve(1),
Promise.resolve(2)
]) // Promise?{<rejected>: 'err'}Promise原理實(shí)現(xiàn)
function Promise(excutor){
const self = this;
self.status = "pending";
self.data = undefined;
self.callbacks = [] // 用來(lái)保存所有待調(diào)用的回調(diào)函數(shù)
function resolve(value){
if(self.status !== "pending"){
return;
}
// 改變狀態(tài)
self.status = "fulfilled";
self.data = value;
// 異步調(diào)用回調(diào)函數(shù)
if(self.callbacks.length > 0){
setTimeout(()=>{
self.callbacks.forEach((obj)=>{
obj.onResolved();
})
})
}
}
function reject(reason){
if(self.status !== "pending"){
return;
}
// 改變狀態(tài)
self.status = "rejected";
self.data = value;
// 異步調(diào)用回調(diào)函數(shù)
if(self.callbacks.length > 0){
setTimeout(()=>{
self.callbacks.forEach((obj)=>{
obj.onRejected();
})
})
}
}
// 立即同步調(diào)用執(zhí)行器函數(shù)
try{
excutor(resolve,reject);
}catch(error){
reject(error);
}
}
Promise.prototype.then = function(onResolved,onRejected){
const self = this;
onResolved = typeof onResolved === 'function'?onResolved:value=>value;
onRejected = typeof onResolved === 'function'?onRejected:reason=>{throw reason};
// 返回promise對(duì)象
return new Promise((resolve,reject)=>{
// 執(zhí)行回調(diào)函數(shù)
function handle(callback){
try{
const x = callback(self.data);
// 如果返回對(duì)象為promise
if(x instanceof Promise){
x.then(resolve,reject);
}else{
resolve(x);
}
}catch(err){
reject(err);
}
}
// 定義回調(diào)之前,狀態(tài)已經(jīng)改變
if(self.status == 'fulfilled'){
setTimeout(()=>{
handle(onResolved);
})
}else if(self.status == 'rejected'){
setTimeout(()=>{
handle(onRejected);
})
}else{
// 定義回調(diào)之前狀態(tài)還未改變
self.callbacks.push({
onResolved(){
handle(onResolved);
},
onRejected(){
handle(onRejected);
}
})
}
})
}
Promise.prototype.catch = function(onRejected){
return this.then(undefined,onRejected)
}
Promise.resolve = function(value){
return new Promise((resolve,reject)=>{
if(value instanceof Promise){
value.then(resolve,reject);
}else{
resolve(value);
}
})
}
Promise.reject = function(reason){
return new Promise((resolve,reject)=>{
reject(reason);
})
}
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let count = 0;
let plen = promises.length;
let values = new Array(plen);
for(let i = 0;i < plen;i++){
Promise.resolve(promises[i]).then(value=>{
count++;
value[i] = value;
if(count == plen) resolve(values);
},err=>{
reject(err);
})
}
})
}
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i < promises.length;i++){
Promise.resolve(promises[i]).then(value=>{
resolve(value);
},err=>{
reject(err);
})
}
})
}異步函數(shù)async/await
ES8提出使用async和await解決異步結(jié)構(gòu)組織代碼的問(wèn)題
async
async關(guān)鍵字用于聲明異步函數(shù),使用async關(guān)鍵字可以讓函數(shù)具有異步特征,但總體上其代碼仍然時(shí)同步求值。
async function fn1(){
console.log(1);
}
fn1();
console.log(2);
// 1
// 2async的返回值是一個(gè)Promise對(duì)象,如果函數(shù)中返回一個(gè)值value,則async則返回Promise.resolve(value);
await
因?yàn)楫惒胶瘮?shù)主要針對(duì)不會(huì)馬上完成的任務(wù),所以自然需要一種暫停和恢復(fù)執(zhí)行的能力。
await關(guān)鍵字必須在異步函數(shù)中使用。
async/await中真正起作用的是await。async關(guān)鍵字,無(wú)論從哪個(gè)方面來(lái)看,都是一個(gè)標(biāo)識(shí)符。異步函數(shù)中不包含await關(guān)鍵字,其執(zhí)行基本上跟普通函數(shù)沒(méi)有區(qū)別。
async function fn(){
console.log(1);
let p = await Promise.resolve('ok');
console.log(p);
console.log(2);
}
fn();
console.log(3);
// 1
// 3
// ok
// 2js事件循環(huán)機(jī)制
js運(yùn)行機(jī)制
因?yàn)閖s時(shí)單線程的,在執(zhí)行代碼時(shí),將不同函數(shù)的執(zhí)行上下文壓入棧中來(lái)保證代碼的有序執(zhí)行,在執(zhí)行同步代碼的時(shí)候,如果遇到異步代碼,js并不會(huì)等待其返回結(jié)果,而是將異步代碼交給對(duì)應(yīng)的異步代碼處理線程處理,繼續(xù)執(zhí)行棧中的任務(wù)。
當(dāng)異步事件執(zhí)行完畢之后,再將異步事件對(duì)應(yīng)的對(duì)調(diào)加入異步執(zhí)行隊(duì)列,當(dāng)主棧中的函數(shù)執(zhí)行完畢后,會(huì)從異步執(zhí)行隊(duì)列中選擇任務(wù)來(lái)執(zhí)行。

宏任務(wù)和微任務(wù)
在任務(wù)隊(duì)列中異步回調(diào)分為2中:微任務(wù)和宏任務(wù)。也就是說(shuō)任務(wù)隊(duì)列可以分為兩種:微任務(wù)隊(duì)列和宏任務(wù)隊(duì)列。
宏任務(wù):全局代碼,settimeout/setinterval,UI渲染,
微任務(wù):promise.then,catch,finally,process.nextTick
宏任務(wù)和微任務(wù)的執(zhí)行順序
代碼從開(kāi)始執(zhí)行調(diào)用全局執(zhí)行棧,script標(biāo)簽內(nèi)的代碼做為宏任務(wù)執(zhí)行。
執(zhí)行過(guò)程中同步代碼立即執(zhí)行,異步代碼由異步處理線程處理然后放入任務(wù)隊(duì)列中。
執(zhí)行過(guò)程中同步代碼立即執(zhí)行,異步代碼放入任務(wù)隊(duì)列中。
先看任務(wù)隊(duì)列中的微任務(wù)隊(duì)列是否存在微任務(wù)
有微任務(wù):執(zhí)行微任務(wù)隊(duì)列中的所有微任務(wù)
微任務(wù)執(zhí)行過(guò)程中所產(chǎn)生的微任務(wù)放到微任務(wù)隊(duì)列中,繼續(xù)執(zhí)行。
如果沒(méi)微任務(wù),查看是否具有宏任務(wù),有的話執(zhí)行,沒(méi)有的話事件輪詢結(jié)束。
執(zhí)行過(guò)程中所產(chǎn)生的微任務(wù)放到微任務(wù)隊(duì)列中。
完成單個(gè)宏任務(wù)之后,執(zhí)行微任務(wù)隊(duì)列中的任務(wù)。
常見(jiàn)面試題
異步編程的實(shí)現(xiàn)方式有哪些
- 回調(diào)函數(shù):使用回調(diào)函數(shù)的方式的缺點(diǎn)是,是個(gè)回調(diào)函數(shù)嵌套的時(shí)候,會(huì)照成回調(diào)函數(shù)地獄,上下兩層的回調(diào)函數(shù)間的代碼耦合度太高,不利于代碼維護(hù)。
- promise:使用promise的方式可以嵌套的函數(shù)調(diào)用做為鏈?zhǔn)秸{(diào)用,但是使用這種方法,有時(shí)會(huì)找出多個(gè)then的鏈?zhǔn)秸{(diào)用,可能造成代碼的語(yǔ)義不夠明確。
- generator:配合yield實(shí)現(xiàn),可以在函數(shù)內(nèi)實(shí)現(xiàn)中斷。
- async/await:async函數(shù)是generator和promise實(shí)現(xiàn)的一個(gè)自動(dòng)執(zhí)行的語(yǔ)法糖,他內(nèi)部自帶執(zhí)行器,當(dāng)函數(shù)內(nèi)部執(zhí)行到await語(yǔ)句的時(shí)候,如果語(yǔ)句返回promise對(duì)象,那么函數(shù)將會(huì)等待promise對(duì)象的狀態(tài)變?yōu)閞esolve后再基于向下執(zhí)行。因此可以將異步邏輯轉(zhuǎn)化為同步的順序來(lái)寫(xiě),并且這個(gè)函數(shù)可以自動(dòng)執(zhí)行。
如何改變promise的狀態(tài)
- 調(diào)用resolve(value),pending ->fulfilled
- 調(diào)用reject(reason),pending ->rejected
- 拋出異常:如果當(dāng)前時(shí)pending就會(huì)變成rejected
改變promise狀態(tài)和指定回調(diào)函數(shù)誰(shuí)先誰(shuí)后
都有可能,正常情況下是先指定回調(diào)再改變狀態(tài),但也可以先改變狀態(tài)再指定回調(diào)。
什么時(shí)候能得到數(shù)據(jù)?
- 先指定回調(diào),當(dāng)狀態(tài)發(fā)生改變時(shí),回調(diào)函數(shù)就會(huì)調(diào)用,得到數(shù)據(jù)。
- 先改變狀態(tài),當(dāng)指定回調(diào)函數(shù)時(shí),回調(diào)函數(shù)就會(huì)調(diào)用,得到數(shù)據(jù)。
promise中改變狀態(tài)和指定回調(diào)函數(shù)時(shí)做了什么事情
在promise內(nèi)部定義了一個(gè)回調(diào)隊(duì)列。
在狀態(tài)改變時(shí),也就是調(diào)用onresolved或者onrejected函數(shù)時(shí),會(huì)執(zhí)行回調(diào)隊(duì)列中的函數(shù)。
當(dāng)指定回調(diào)函數(shù)時(shí),首先會(huì)判斷當(dāng)前的狀態(tài)是否是pending,如果是則將回調(diào)函數(shù)加入回調(diào)隊(duì)列,否則直接執(zhí)行根據(jù)狀態(tài)執(zhí)行onresolved或者onrejected函數(shù)。
這就是為什么無(wú)論是先改變狀態(tài)還是先指定回調(diào)函數(shù),都能得到最終的結(jié)果的原因。
promise.then() 返回的新promise的結(jié)果由什么決定
如果拋出異常,新的promise變?yōu)閞eject,reason為拋出的異常
如果返回的時(shí)非promise的任意值,新promise變?yōu)閒ulfilled,value為返回的值
如果返回promise的時(shí)另一個(gè)promise對(duì)象,此peomise的結(jié)果就會(huì)成為新promise的結(jié)果。
promise異常穿透
- 當(dāng)使用promise的then鏈?zhǔn)秸{(diào)用時(shí),可以在最后指定失敗的回調(diào)
- 前面任何操作處理異常都會(huì)傳到最后的回調(diào)中處理
中斷promise鏈
當(dāng)使用promise的then鏈?zhǔn)秸{(diào)用,在中間中斷,不在調(diào)用后面的回調(diào)函數(shù)。
辦法:在回調(diào)函數(shù)中返回一個(gè)pending狀態(tài)的promise對(duì)象
async/await對(duì)比promise的優(yōu)勢(shì)
- 代碼讀起來(lái)更像同步,Promise雖然擺脫了回調(diào)地獄,但是then方法的鏈?zhǔn)秸{(diào)用也會(huì)帶來(lái)額外的閱讀負(fù)擔(dān)
- Promise傳遞中間值非常麻煩,而async、await幾乎是同步的寫(xiě)法
Promist.catch后面的.then還會(huì)執(zhí)行嗎
.then回執(zhí)行,因?yàn)閏atch方法返回的還是一個(gè)promise對(duì)象,依然支持鏈?zhǔn)秸{(diào)用。
Promise中,resolve后面的語(yǔ)句是否還會(huì)執(zhí)行?
會(huì)被執(zhí)行。如果不需要執(zhí)行,需要在 resolve 語(yǔ)句前加上 return。
JS為什么是單線程語(yǔ)言
JavaScript的誕生就是為了處理瀏覽器網(wǎng)頁(yè)的交互(DOM操作的處理、UI動(dòng)畫(huà)等), 設(shè)計(jì)成單線程的原因就是不想讓瀏覽器變得太復(fù)雜,因?yàn)槎嗑€程需要共享資源、且有可能修改彼此的運(yùn)行結(jié)果(兩個(gè)線程修改了同一個(gè)DOM節(jié)點(diǎn)就會(huì)產(chǎn)生不必要的麻煩),這對(duì)于一種網(wǎng)頁(yè)腳本語(yǔ)言來(lái)說(shuō)這就太復(fù)雜了。
為了利用多核CPU的計(jì)算能力,HTML5提出Web Worker標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個(gè)線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個(gè)新標(biāo)準(zhǔn)并沒(méi)有改變JavaScript單線程的本質(zhì)。
JS是怎么實(shí)現(xiàn)異步異常運(yùn)行的
JavaScript是單線程的,但它所運(yùn)行的宿主環(huán)境—瀏覽器是多線程,瀏覽器提供了各種線程供Event Loop調(diào)度來(lái)協(xié)調(diào)JS單線程運(yùn)行時(shí)不會(huì)阻塞
談一談setInterval的問(wèn)題
setTimeout的作用是每隔一段指定事件執(zhí)行一個(gè)函數(shù),但是這個(gè)執(zhí)行不是真的到了時(shí)間立即執(zhí)行,它真正的作用是每隔一段時(shí)間將事件加入事件隊(duì)列中去,只有當(dāng)當(dāng)前的執(zhí)行棧為空的時(shí)候,才能去從事件隊(duì)列中取出事件執(zhí)行。
所以可能會(huì)出現(xiàn)這樣的情況,就是當(dāng)前執(zhí)行棧執(zhí)行的時(shí)間很長(zhǎng),導(dǎo)致事件隊(duì)列里邊積累多個(gè)定時(shí)器加入的事件,當(dāng)執(zhí)行棧結(jié)束的時(shí)候,這些事件會(huì)依次執(zhí)行,因此就不能到間隔一段時(shí)間執(zhí)行的效果。
正對(duì)這種現(xiàn)象,我們可以使用setTimeout遞歸調(diào)用來(lái)模擬實(shí)現(xiàn)setInterval,這樣就能確保了只有一個(gè)事件結(jié)束,我們才會(huì)觸發(fā)下一個(gè)定時(shí)器事件。
function mySetInterval(fn,delay){
var timer = {
flag = true;
};
function interval(){
if(timer.flag){
fn();
setTimeout(interval,delay);
}
}
setTimeout(intetval,delay);
return timer;
}
function myClearInterval(timer){
timer.flag = false;
}以上就是深入探究JS中的異步編程和事件循環(huán)機(jī)制的詳細(xì)內(nèi)容,更多關(guān)于JS異步編程和事件循環(huán)機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
AngularJs+Bootstrap實(shí)現(xiàn)漂亮的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了angularJs+Bootstrap實(shí)現(xiàn)漂亮的計(jì)算器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
JavaScript隨機(jī)數(shù)生成各種技巧及實(shí)例代碼
這篇文章主要介紹了JavaScript隨機(jī)數(shù)生成各種技巧及實(shí)例代碼,包括生成0到1之間的隨機(jī)浮點(diǎn)數(shù)、指定范圍的隨機(jī)整數(shù)和浮點(diǎn)數(shù)、從數(shù)組中隨機(jī)選擇元素、生成隨機(jī)顏色以及生成指定數(shù)目和范圍的隨機(jī)數(shù),文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-04-04
微信sdk實(shí)現(xiàn)禁止微信分享(使用原生php實(shí)現(xiàn))
這篇文章主要介紹了微信sdk實(shí)現(xiàn)禁止微信分享(使用原生php實(shí)現(xiàn)),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Js中安全獲取Object深層對(duì)象的方法實(shí)例
Object是JavaScript基本數(shù)據(jù)類型之一(function也屬于object,是特殊的object),其存儲(chǔ)于堆中,這篇文章主要給大家介紹了關(guān)于Js中安全獲取Object深層對(duì)象的相關(guān)資料,需要的朋友可以參考下2021-09-09
20行JS代碼實(shí)現(xiàn)網(wǎng)頁(yè)刮刮樂(lè)效果
下面小編就為大家?guī)?lái)一篇20行JS代碼實(shí)現(xiàn)網(wǎng)頁(yè)刮刮樂(lè)效果。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
微信小程序?qū)崿F(xiàn)的canvas合成圖片功能示例
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)的canvas合成圖片功能,結(jié)合實(shí)例形式分析了微信小程序canvas合成圖片相關(guān)組件使用、操作步驟與注意事項(xiàng),需要的朋友可以參考下2019-05-05
JS實(shí)現(xiàn)仿UC瀏覽器前進(jìn)后退效果的實(shí)例代碼
這篇文章主要介紹了JS實(shí)現(xiàn)仿UC瀏覽器前進(jìn)后退效果的實(shí)例代碼,實(shí)現(xiàn)此功能前需要先測(cè)試下瀏覽器,具體實(shí)例代碼,大家參考下本文2017-07-07
JavaScript之IE的fireEvent方法詳細(xì)解析
剛開(kāi)始我以為是會(huì)跟平時(shí)使用onclick()一樣,沒(méi)想到最近在寫(xiě)javascript入門(mén)ppt的時(shí)候發(fā)現(xiàn)了,原來(lái)自己太自以為是了!看來(lái)還有很多javascript的細(xì)節(jié)沒(méi)有掌握好啊2013-11-11

