Javascript 異步編程(三)
并行阱驾?并發(fā)?異步昔榴?
同步:synchronous: 指所有任務(wù)按出現(xiàn)的先后順序依次執(zhí)行 如果出現(xiàn)阻塞的任務(wù)逞怨,那么線程就會(huì)等待這個(gè)任務(wù)完成,接著執(zhí)行下一個(gè)任務(wù)槽惫。
異步:asynchronous:不保證所有任務(wù)按出現(xiàn)的順序執(zhí)行
并發(fā):concurrent:從宏觀上周叮,某個(gè)時(shí)間段里面多個(gè)程序都得到了運(yùn)行,但不是說(shuō)“同時(shí)運(yùn)行”
并行:parallel:在多核心下界斜,因進(jìn)程和線程獨(dú)立運(yùn)行仿耽,且多個(gè)線程之間共享數(shù)據(jù),程序可以同時(shí)運(yùn)行各薇。
定時(shí)器
常用的回調(diào)函數(shù)有:
- setTimeout
- setInterval
- setImmediate(Node.js)
- requestAnimationFrame
https://zhuanlan.zhihu.com/p/55129100
setTimeout
作用:延遲指定的時(shí)間來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式项贺。
語(yǔ)法:setTimeout(func /**函數(shù),必選*/,code /**表達(dá)式君躺,可選*/ ,milliseconds /**執(zhí)行需等待的毫秒數(shù),必選*/param1, param2, .../**傳遞給函數(shù)的參數(shù)开缎,可選*/)
具體用法參加《Javascript異步編程(一)》
setInterval
作用:按照指定的周期(以毫秒計(jì))來(lái)調(diào)用函數(shù)或計(jì)算表達(dá)式棕叫。
語(yǔ)法:setInterVal(func /**函數(shù),必選*/,code /**表達(dá)式,可選*/ ,milliseconds /**每次執(zhí)行將延遲的毫秒數(shù)奕删,必選*/param1, param2, .../**傳遞給函數(shù)的參數(shù)俺泣,可選*/)
原理
console.log('sync...',1);
setInterval(()=>{
console.log('sync...',2)
},2000);
console.log('sync...',3)
流程分析:
- 主程序調(diào)用棧
- 調(diào)用webAPI-
setInterVal
- 定時(shí)器線程計(jì)數(shù)2s
- 每隔2s事件觸發(fā)線程將回調(diào)放入任務(wù)隊(duì)列
- 主線程通過(guò)Event Loop遍歷任務(wù)隊(duì)列執(zhí)行回調(diào)
console.log('sync...',2)
注意
- IE9及以下版本不兼容(
setTimeout
&setInterval
)傳遞額外參數(shù),可使用polyfill - setInterval完残,delay最小為10ms伏钠。意味著無(wú)法設(shè)置為0秒間隔,但可以嘗試使用postMessage實(shí)現(xiàn)
- 使用
clearInterval(timerId)
關(guān)閉指定定時(shí)器 - 確苯魃瑁回調(diào)函數(shù)執(zhí)行時(shí)長(zhǎng)小于delay時(shí)間
- 同setTimeout一樣熟掂,setInterval回調(diào)中的this永遠(yuǎn)指向global
- 不推薦使用
setInterval(code,delay)
,有安全風(fēng)險(xiǎn)扎拣。
為什么不建議使用setInterval
如果任務(wù)實(shí)際耗時(shí)超過(guò)delay,會(huì)出現(xiàn)同一時(shí)間觸發(fā)多個(gè)回調(diào)赴肚。
有以下場(chǎng)景,每隔1s調(diào)用服務(wù)
// 時(shí)間間隔大于delay
let count = 5
let intervalTimer = setInterval(function () {
if (count <= 0) {
clearInterval(intervalTimer)
return
}
/*模擬延時(shí)任務(wù)*/
let timeoutTimer = setTimeout(function (count) {
console.log(`the ${count} is running`)
clearTimeout(timeoutTimer)
}, Math.floor(Math.random() * (10000) + 1000),count)
count--
}, 1000)
從上圖可知二蓝,xhr響應(yīng)無(wú)法按照順序返回誉券,這樣就會(huì)導(dǎo)致無(wú)法正常處理結(jié)果
折中方案
function moreBetterInterval (count) {
// 1s后調(diào)用
setTimeout(function (countDown) {
console.log(count +' is begin')
let timeoutTimer = setTimeout(function (times) {
console.log(`the ${times} is running`)
clearTimeout(timeoutTimer)
times--;
if(times>0){
moreBetterInterval(times)
}
}, Math.floor(Math.random() * (10000) + 1000),countDown)
}, 1000,count)
}
moreBetterInterval(5)
可以保證遞歸之前已執(zhí)行完回調(diào),但無(wú)法保證按照一定的時(shí)間間隔侣夷。
實(shí)際應(yīng)用場(chǎng)景
- 短信倒計(jì)時(shí)
let countDown=60;
let timer=setInterval(function () {
countDown--;
if(countDown<0){
clearInterval(timer);
countDown=60
}
},1000)
- 顯示當(dāng)前時(shí)間
setInterval(function () {
let d = new Date()
$('#clock')[0].innerHTML = d.toLocaleTimeString()
}, 1000)
setImmediate
僅在Internet Explorer和Node.js下可用
作用:在循環(huán)事件任務(wù)完成后馬上運(yùn)行指定代碼
語(yǔ)法:setImmediate(func /**函數(shù),必選*/,code /**表達(dá)式横朋,可選*/,[ param1,param2百拓,...]/**傳遞給函數(shù)的參數(shù)琴锭,可選*/)
;
setImmediate與setTimeout(func,0)?
setTimeout(func,0)
- 在HTML5中衙传,如果是0决帖,會(huì)根據(jù)嵌套層數(shù)初始化不小于4ms,各瀏覽器具體實(shí)現(xiàn)不同
- 在Node.js中,如果是0蓖捶,則初始化為1ms
誰(shuí)先誰(shuí)后地回,不一定~
需要考慮
- 是在哪個(gè)階段注冊(cè)的,如果在定時(shí)器回調(diào)或者I/O回調(diào)里面俊鱼,setImmediate肯定先執(zhí)行刻像。如果在最外層或者setImmediate回調(diào)里面,哪個(gè)先執(zhí)行取決于當(dāng)時(shí)機(jī)器狀況并闲。
- 當(dāng)前的Event Loop的任務(wù)隊(duì)列的情況细睡,如果在隊(duì)尾,那也要先執(zhí)行前面的事件帝火,這樣也無(wú)法保證立即執(zhí)行
requestAnimationFrame
作用:接受一個(gè)動(dòng)畫(huà)執(zhí)行函數(shù)作為參數(shù)溜徙,這個(gè)函數(shù)的作用是僅執(zhí)行一幀動(dòng)畫(huà)的渲染湃缎,并根據(jù)條件判斷是否結(jié)束,如果動(dòng)畫(huà)沒(méi)有結(jié)束蠢壹,則繼續(xù)調(diào)用requestAnimationFrame并將自身作為參數(shù)傳入
語(yǔ)法:requestAnimationFrame(func /**函數(shù)嗓违,必選*/)
細(xì)節(jié):以60FPS(每幀16.7ms)為目標(biāo),瀏覽器內(nèi)部會(huì)選擇渲染的最佳時(shí)機(jī)
與setTimeout動(dòng)畫(huà)區(qū)別:
-
setTimeout(func,16.7)
:容易卡頓
原因有兩個(gè):
- 實(shí)際執(zhí)行時(shí)間晚于設(shè)定的延遲時(shí)間,出現(xiàn)卡頓
- 與瀏覽器刷新率有關(guān)图贸,不同設(shè)備的屏幕刷新頻率可能會(huì)不同蹂季,而setTimeout只能設(shè)定固定的時(shí)間間隔,無(wú)法保證與刷新率同步疏日,容易丟幀
-
requestAnimationFrame
能節(jié)省CPU開(kāi)銷(xiāo)乏盐,當(dāng)元素隱藏或不可見(jiàn)時(shí),會(huì)停止渲染制恍。而setTimeout仍在后臺(tái)執(zhí)行。
用途
- 實(shí)現(xiàn)一幀的函數(shù)節(jié)流
- 動(dòng)畫(huà)
注意
- 使用
cancelAnimationFrame(id)
關(guān)閉渲染動(dòng)畫(huà) - IE10以下不兼容神凑,可使用setTimeout進(jìn)行polyfill 從而模擬幀率盡量適配刷新率
結(jié)尾
通過(guò)以上定時(shí)器净神,最顯著的共同點(diǎn)是:回調(diào)。
初一看溉委,回調(diào)沒(méi)有問(wèn)題呀鹃唯,可以延遲計(jì)算。請(qǐng)想下以下情景:
- 嵌套回調(diào)
let msg = document.getElementById('msg')
$('#btn').click(function (evt) {
msg.innerHTML += `${new Date()} processing btn click callback... <br>`
setTimeout(function request () {
msg.innerHTML += `${new Date()} processing setTimeout callback...<br>`
$.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
msg.innerHTML += `${new Date()} processing ajax callback...<br>`
})
}, 500)
})
這里我們用了三個(gè)函數(shù)嵌套瓣喊,這種代碼就被稱(chēng)為“回調(diào)地獄(callback hell)”坡慌,這樣的代碼難以編寫(xiě),難以理解而且難以維護(hù)
- 控制反轉(zhuǎn)
$.get('https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js', function (data, status) {
msg.innerHTML += `${new Date()} processing ajax callback...<br>`
})
即自己程序的一部分的執(zhí)行控制權(quán)交由某個(gè)第三方藻三。在你不確定這個(gè)回調(diào)能否按照預(yù)期執(zhí)行時(shí)洪橘,發(fā)生意外時(shí)很難定位問(wèn)題。
為了優(yōu)雅的的處理回調(diào)最大的問(wèn)題:控制反轉(zhuǎn)棵帽,有以下方式:
- 回調(diào)分離 -->ES6 Promise
-
error-first
風(fēng)格-->Node.js中會(huì)將回調(diào)的第一個(gè)參數(shù)保留用作error
const fs=require('fs')
fs.readFile(__dirname,function(err,data) {
if (err) console.log(err)
//...
})
但還是不優(yōu)雅熄求,并沒(méi)有真正解決我們的控制反轉(zhuǎn)問(wèn)題,只是將我們之前擔(dān)心的程序異常暴露了出來(lái)逗概。
可能現(xiàn)在你希望有API或其他語(yǔ)言機(jī)制來(lái)解決這些問(wèn)題弟晚。所幸,ES6會(huì)給你帶來(lái)些干貨~