同步模式與異步模式
事件循環(huán)與消息隊(duì)列
??JavaScript 單線程指的是瀏覽器中負(fù)責(zé)解釋和執(zhí)行 JavaScript 代碼的只有一個線程拘鞋,即為JS引擎線程,但是瀏覽器的渲染進(jìn)程是提供多個線程的矢门,如下:
- JS引擎線程
- 事件觸發(fā)線程
- 定時(shí)觸發(fā)器線程
- 異步http請求線程
- GUI渲染線程
??當(dāng)遇到計(jì)時(shí)器盆色、DOM事件監(jiān)聽或者是網(wǎng)絡(luò)請求的任務(wù)時(shí),JS引擎會將它們直接交給 webapi祟剔,也就是瀏覽器提供的相應(yīng)線程(如定時(shí)器線程為setTimeout計(jì)時(shí)隔躲、異步http請求線程處理網(wǎng)絡(luò)請求)去處理,而JS引擎線程繼續(xù)后面的其他任務(wù)物延,這樣便實(shí)現(xiàn)了 異步非阻塞宣旱。
??定時(shí)器觸發(fā)線程也只是為 setTimeout(..., 1000) 定時(shí)而已,時(shí)間一到叛薯,還會把它對應(yīng)的回調(diào)函數(shù)(callback)交給 任務(wù)隊(duì)列 去維護(hù)浑吟,JS引擎線程會在適當(dāng)?shù)臅r(shí)候去任務(wù)隊(duì)列取出任務(wù)并執(zhí)行。
JS引擎線程什么時(shí)候去處理呢耗溜?消息隊(duì)列又是什么组力?
JavaScript 通過 事件循環(huán) event loop 的機(jī)制來解決這個問題。
其實(shí) 事件循環(huán) 機(jī)制和 任務(wù)隊(duì)列 的維護(hù)是由事件觸發(fā)線程控制的抖拴。
事件觸發(fā)線程 同樣是瀏覽器渲染引擎提供的燎字,它會維護(hù)一個 任務(wù)隊(duì)列。
??JS引擎線程遇到異步(DOM事件監(jiān)聽阿宅、網(wǎng)絡(luò)請求候衍、setTimeout計(jì)時(shí)器等...),會交給相應(yīng)的線程單獨(dú)去維護(hù)異步任務(wù)家夺,等待某個時(shí)機(jī)(計(jì)時(shí)器結(jié)束脱柱、網(wǎng)絡(luò)請求成功、用戶點(diǎn)擊DOM)拉馋,然后由 事件觸發(fā)線程 將異步對應(yīng)的 回調(diào)函數(shù) 加入到消息隊(duì)列中榨为,消息隊(duì)列中的回調(diào)函數(shù)等待被執(zhí)行。
同時(shí)煌茴,JS引擎線程會維護(hù)一個 執(zhí)行棧随闺,同步代碼會依次加入執(zhí)行棧然后執(zhí)行,結(jié)束會退出執(zhí)行棧蔓腐。
如果執(zhí)行棧里的任務(wù)執(zhí)行完成矩乐,即執(zhí)行棧為空的時(shí)候(即JS引擎線程空閑),事件觸發(fā)線程才會從消息隊(duì)列取出一個任務(wù)(即異步的回調(diào)函數(shù))放入執(zhí)行棧中執(zhí)行。
消息隊(duì)列是類似隊(duì)列的數(shù)據(jù)結(jié)構(gòu)散罕,遵循先入先出(FIFO)的規(guī)則分歇。
- 所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)欧漱。
- 主線程之外职抡,還存在一個"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果误甚,就在"任務(wù)隊(duì)列"之中放置一個事件缚甩。
- 一但"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊(duì)列"窑邦,看看里面有哪些事件擅威。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài)冈钦,進(jìn)入執(zhí)行棧郊丛,開始執(zhí)行。
- 主線程不斷重復(fù)上面的第三步派继。
只要主線程空了宾袜,就會去讀取"任務(wù)隊(duì)列",這就是JavaScript的運(yùn)行機(jī)制驾窟。這個過程會不斷重復(fù)庆猫,這種機(jī)制就被稱為事件循環(huán)(event loop)機(jī)制。
異步編程的幾種方式
Promise異步方案 宏任務(wù)/微任務(wù)隊(duì)列
Promise
- promise是一個類绅络,在執(zhí)行這個類的時(shí)候需要傳遞一個執(zhí)行器進(jìn)去 執(zhí)行器月培,執(zhí)行器會立即執(zhí)行
- promise中有三種狀態(tài) 分別為成功fulfilled 失敗rejected 等待pending
一旦狀態(tài)確定就不可更改 - resolve和reject函數(shù)是用來更改狀態(tài)的
- then方法內(nèi)部做的事情就是判斷狀態(tài),成功就調(diào)用成功函數(shù)恩急,反之調(diào)用失敗函數(shù)杉畜,then方法是被定義到原型對象中的
- then成功回調(diào)有一個參數(shù)表示成功之后的值,同樣失敗也有參數(shù)
- then方法是可以被鏈?zhǔn)秸{(diào)用的衷恭,后面的then拿到的值是此叠,是上一個then的返回值
const PENDING='pending'
const FULFILLED='fulfilled'
const REJECTED='rejected'
class MyPromise{
construtor(executor){
try{
executor(this.resolve,this.reject)
}catch(e){
this.reject(e)
}
}
status=PENDING
//成功之后的值
value=undefined
//失敗后的原因
reason=undefined
//成功回調(diào)
successCallback=[]
//失敗回調(diào)
failCallback=[]
resolve=(value)=>{
//如果狀態(tài)不是等待阻止程序向下執(zhí)行
if(this.status!==PENDING) return;
this.status=FULFILLED
//保存成功之后的值
this.value=value
while(this.successCallback.length){
this.successCallback.shift()()
}
}
reject=()=>{
if(this.status!==PENDING) return随珠;
this.status=REJECTED
//保存失敗后的原因
this.reason=reason
while(this.failCallback.length){
this.failCallback.shift()()
}
}
then(successCallback,failCallback){
let promise2=new MyPromise((resolve,reject)=>{
if(this.status===FULFILLED){
setTimeout(()=>{
try{
let x= successCallback(this.value)
//判斷x的值是普通值還是promise對象
//如果是普通值直接調(diào)用resolve
//如果是promise對象查看promise對象的返回結(jié)果
//再根據(jù)promise對象返回的結(jié)果決定調(diào)用resolve還是reject
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}
if(this.status===REJECTED){
setTimeout(()=>{
try{
let x= failCallback(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
}else{
//等待,存儲成功和失敗回調(diào)
this.successCallback.push(()=>{
setTimeout(()=>{
try{
let x= successCallback(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
this.failCallback.push(()=>{
setTimeout(()=>{
try{
let x= failCallback(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
},0)
})
}
})
return promise2
}
}
function resolvePromise(promise2,x,resolve,reject){
if(x===promise2){
reject(new TypeError('chaining cycle detected for promise'))
}
if(x instanceof MyPromise){
//x.then(value=>resolve(value),reason=>reject(reason))
x.then(resolve,reject)
}else{
resolve(x)
}
}
macro-task(宏任務(wù)) 和 micro-task(微任務(wù))灭袁。
所有任務(wù)分為 macro-task 和 micro-task:
- macro-task:主代碼塊、setTimeout窗看、setInterval等(可以看到茸歧,事件隊(duì)列中的每一個事件都是一個 macro-task,現(xiàn)在稱之為宏任務(wù)隊(duì)列)
- micro-task:Promise显沈、process.nextTick等
JS引擎線程首先執(zhí)行主代碼塊软瞎。
每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù)逢唤,包括任務(wù)隊(duì)列(宏任務(wù)隊(duì)列)中的,因?yàn)閳?zhí)行棧中的宏任務(wù)執(zhí)行完會去取任務(wù)隊(duì)列(宏任務(wù)隊(duì)列)中的任務(wù)加入執(zhí)行棧中涤浇,即同樣是事件循環(huán)的機(jī)制鳖藕。
在執(zhí)行宏任務(wù)時(shí)遇到Promise等,會創(chuàng)建微任務(wù)(.then()里面的回調(diào))只锭,并加入到微任務(wù)隊(duì)列隊(duì)尾吊奢。
micro-task必然是在某個宏任務(wù)執(zhí)行的時(shí)候創(chuàng)建的,而在下一個宏任務(wù)開始之前纹烹,瀏覽器會對頁面重新渲染(task >> 渲染 >> 下一個task(從任務(wù)隊(duì)列中取一個))。同時(shí)召边,在上一個宏任務(wù)執(zhí)行完成后铺呵,渲染頁面之前,會執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)隧熙。
也就是說片挂,在某一個macro-task執(zhí)行完后,在重新渲染與開始下一個宏任務(wù)之前贞盯,就會將在它執(zhí)行期間產(chǎn)生的所有micro-task都執(zhí)行完畢(在渲染前)音念。
這樣就可以解釋 "promise 1" "promise 2" 在 "timer over" 之前打印了。"promise 1" "promise 2" 做為微任務(wù)加入到微任務(wù)隊(duì)列中躏敢,而 "timer over" 做為宏任務(wù)加入到宏任務(wù)隊(duì)列中闷愤,它們同時(shí)在等待被執(zhí)行,但是微任務(wù)隊(duì)列中的所有微任務(wù)都會在開始下一個宏任務(wù)之前都被執(zhí)行完件余。
在node環(huán)境下讥脐,process.nextTick的優(yōu)先級高于Promise,也就是說:在宏任務(wù)結(jié)束后會先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue啼器,然后才會執(zhí)行微任務(wù)中的Promise旬渠。
執(zhí)行機(jī)制:
- 執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊(duì)列中獲取)
- 執(zhí)行過程中如果遇到微任務(wù)端壳,就將它添加到微任務(wù)的任務(wù)隊(duì)列中
- 宏任務(wù)執(zhí)行完畢后告丢,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
- 當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染损谦,然后GUI線程接管渲染
- 渲染完畢后岖免,JS引擎線程繼續(xù),開始下一個宏任務(wù)(從宏任務(wù)隊(duì)列中獲瘸婶妗)
宏任務(wù) macro-task(Task)
一個event loop有一個或者多個task隊(duì)列觅捆。task任務(wù)源非常寬泛,比如ajax的onload麻敌,click事件栅炒,基本上我們經(jīng)常綁定的各種事件都是task任務(wù)源,還有數(shù)據(jù)庫操作(IndexedDB ),需要注意的是setTimeout赢赊、setInterval乙漓、setImmediate也是task任務(wù)源∈鸵疲總結(jié)來說task任務(wù)源:
- script
- setTimeout
- setInterval
- setImmediate
- I/O
- requestAnimationFrame
- UI rendering
微任務(wù) micro-task(Job)
microtask 隊(duì)列和task 隊(duì)列有些相似叭披,都是先進(jìn)先出的隊(duì)列,由指定的任務(wù)源去提供任務(wù)玩讳,不同的是一個 event loop里只有一個microtask 隊(duì)列涩蜘。另外microtask執(zhí)行時(shí)機(jī)和Macrotasks也有所差異
- process.nextTick
- promises
- Object.observe
- MutationObserver
宏任務(wù)和微任務(wù)的區(qū)別
- 宏隊(duì)列可以有多個,微任務(wù)隊(duì)列只有一個,所以每創(chuàng)建一個新的settimeout都是一個新的宏任務(wù)隊(duì)列熏纯,執(zhí)行完一個宏任務(wù)隊(duì)列后同诫,都會去checkpoint 微任務(wù)。
- 一個事件循環(huán)后樟澜,微任務(wù)隊(duì)列執(zhí)行完了误窖,再執(zhí)行宏任務(wù)隊(duì)列
- 一個事件循環(huán)中,在執(zhí)行完一個宏隊(duì)列之后秩贰,就會去check 微任務(wù)隊(duì)列