摘要:而同步和異步則是描述另一個(gè)方面。異步將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的操作由系統(tǒng)自動(dòng)處理,然后通知應(yīng)用程序直接使用數(shù)據(jù)即可。
前言
?上周5在公司作了關(guān)于JS異步編程模型的技術(shù)分享,可能是內(nèi)容太干的緣故吧,最后從大家的表情看出“這條粉腸到底在說(shuō)啥?”的結(jié)果:(下面是PPT的講義,具體的PPT和示例代碼在https://github.com/fsjohnhuan...上,有興趣就上去看看吧!
重申主題?《異步編程模型》這個(gè)名稱確實(shí)不太直觀,其實(shí)今天我想和大家分享的就是上面的代碼是如何演進(jìn)成下面的代碼而已。
a(function(){ b(function(){ c(function(){ d() }) }) })
TO
;(async function(){ await a() await b() await c() await d() }())寫(xiě)在前面
?我們知道JavaScript是單線程運(yùn)行的(撇開(kāi)Web Worker),并且JavaScript線程執(zhí)行時(shí)瀏覽器GUI渲染線程無(wú)法搶占CPU時(shí)間片,因此假如我們通過(guò)以下代碼實(shí)現(xiàn)60秒后執(zhí)行某項(xiàng)操作
const deadline = Date.now() + 60000 while(deadline > Date.now()); console.log("doSomething")
那么瀏覽器將假死60秒。正常情況下我們采用異步調(diào)用的方式來(lái)實(shí)現(xiàn)
const deadline = Date.now() + 60000 ;(function _(){ if (deadline > Date.now()){ setTimeout(_, 100) } else{ console.log("doSomething") } }())
那到底上述兩種方式有什么不同呢?
到這里我有個(gè)疑問(wèn),那就是到底什么才叫做異步呢?既然有異步,那必然有同步,那同步又是什么呢?談起同步和異步,那必不可少地要提起阻塞和非阻塞,那它們又是什么意思呢?
談到它們那必須聯(lián)系到IO來(lái)說(shuō)了
阻塞: 就是JS線程發(fā)起阻塞IO后,JS線程什么都不做就等則阻塞IO響應(yīng)。
非阻塞: 就是JS線程發(fā)起非阻塞IO后,JS線程可以做其他事,然后通過(guò)輪詢、信號(hào)量等方式通知JS線程獲取IO響應(yīng)結(jié)果。
也就是說(shuō)阻塞和非阻塞描述的是發(fā)起IO和獲取IO響應(yīng)之間的時(shí)間里,JS線程是否可以繼續(xù)處理其他任務(wù)。
而同步和異步則是描述另一個(gè)方面。
首先當(dāng)我們發(fā)起網(wǎng)絡(luò)IO請(qǐng)求時(shí),應(yīng)用程序會(huì)向OS發(fā)起系統(tǒng)調(diào)用,然后內(nèi)核會(huì)調(diào)用驅(qū)動(dòng)程序操作網(wǎng)卡,然后網(wǎng)卡得到的數(shù)據(jù)會(huì)先存放在內(nèi)核空間中(應(yīng)用程序是讀取不了的),然后將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間。抽象一下就是,發(fā)起IO請(qǐng)求會(huì)涉及到用戶空間和內(nèi)核空間間的數(shù)據(jù)通信。
同步: 應(yīng)用程序需要顯式地將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間中,然后再使用數(shù)據(jù)。
異步: 將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的操作由系統(tǒng)自動(dòng)處理,然后通知應(yīng)用程序直接使用數(shù)據(jù)即可。
對(duì)于如setTimeout等方法而已,本來(lái)就存在用戶空間和內(nèi)核空間的數(shù)據(jù)通信問(wèn)題,因此異步更多是描述非阻塞這一特性。
那么異步調(diào)用的特點(diǎn)就是:
1. 非阻塞
2. 操作結(jié)果將于不明確的未來(lái)返回
舉個(gè)栗子——番茄炒蛋
番茄切塊(代號(hào)a)
雞蛋打成蛋液(代號(hào)b)
蛋液煮成半熟(代號(hào)c)
將蛋切成塊(代號(hào)d)
番茄與雞蛋塊一起炒熟(代號(hào)e)
->番茄切塊->雞蛋打成蛋液->蛋液煮成半熟->將蛋切成塊->番茄與雞蛋塊一起炒熟
a() b() c() d() e()假設(shè)個(gè)步驟都是異步IO時(shí)
?情況1——所有步驟均無(wú)狀態(tài)依賴
->番茄切塊
->雞蛋打成蛋液
->蛋液煮成半熟
->將蛋切成塊
->番茄與雞蛋塊一起炒熟
a() b() c() d() e()
?情況2——步驟間存在線性的狀態(tài)依賴
->番茄切塊->雞蛋打成蛋液->蛋液煮成半熟->將蛋切成塊->番茄與雞蛋塊一起炒熟
a("番茄", function(v番茄塊){ b("雞蛋", function(v蛋液){ c(v蛋液, function(v半熟的雞蛋){ d(v半熟的雞蛋, function(v雞蛋塊){ e(v番茄塊, v雞蛋塊) }) }) }) })
這就是Callback Hell了
?情況3——步驟間存在復(fù)雜的狀態(tài)依賴
異步執(zhí)行:->番茄切塊 |->番茄與雞蛋塊一起炒熟
->雞蛋打成蛋液->蛋液煮成半熟->切成蛋塊|
異步調(diào)用所帶來(lái)的問(wèn)題是
狀態(tài)依賴關(guān)系難以表達(dá),更無(wú)法使用if...else,while等流程控制語(yǔ)句。
無(wú)法提供try...catch異常機(jī)制來(lái)處理異常
初次嘗試——EventProxyEventProxy作為一個(gè)事件系統(tǒng),通過(guò)after、tail等事件訂閱方法提供帶約束的事件觸發(fā)機(jī)制,“約束”對(duì)應(yīng)“前置條件”,因此我們可以利用這種帶約束的事件觸發(fā)機(jī)制來(lái)作為異步執(zhí)行模式下的流程控制表達(dá)方式。
const doAsyncIO = (value, cb) => setTimeout(()=>cb(value), Math.random() * 1000) const ep = new EventProxy() /* 定義任務(wù) */ const a = v番茄 => doAsyncIO("番茄塊", ep.emit.bind(ep,"a")) const b = v雞蛋 => doAsyncIO("蛋液", ep.emit.bind(ep,"b")) const c = v蛋液 => doAsyncIO("半熟的雞蛋", ep.emit.bind(ep,"c")) const d = v半熟的雞蛋 => doAsyncIO("雞蛋塊", ep.emit.bind(ep,"d")) const e = (v番茄塊, v雞蛋塊) => doAsyncIO("番茄炒雞蛋", ep.emit.bind(ep,"e")) /* 定義任務(wù)間的狀態(tài)依賴 */ ep.once("b",c) ep.once("c",d) ep.all("a", "d", e) /* 執(zhí)行任務(wù) */ a() b()
另外通過(guò)error事件提供對(duì)異常機(jī)制的支持
ep.on("error", err => { console.log(err) })
但由于EventProxy采用事件機(jī)制來(lái)做流程控制,而事件機(jī)制好處是降低模塊的耦合度,但從另一個(gè)角度來(lái)說(shuō)會(huì)使整個(gè)系統(tǒng)結(jié)構(gòu)松散難以看出主干模塊,因此通過(guò)事件機(jī)制實(shí)現(xiàn)流程控制必然導(dǎo)致代碼結(jié)構(gòu)松散和邏輯離散,不過(guò)這可以良好的組織形式來(lái)讓代碼結(jié)構(gòu)更緊密一些。
曙光的出現(xiàn)——Promise這里的Promise指的是已經(jīng)被ES6納入囊中的Promises/A+規(guī)范及其實(shí)現(xiàn).
Promise相當(dāng)于我們?nèi)湲?dāng)勞點(diǎn)餐后得到的小票,在未來(lái)某個(gè)時(shí)間點(diǎn)拿著小票就可以拿到食物。不同的是,只要我們持有Promise實(shí)例,無(wú)論索取多少次,都能拿到同樣的結(jié)果。而麥當(dāng)勞顯然只能給你一份食物而已。
代碼表現(xiàn)如下
const p1 = new Promise(function(resolve, reject){ /* 工廠函數(shù) * resolve函數(shù)表示當(dāng)前Promise正常結(jié)束, 例子: setTimeout(()=>resolve("bingo"), 1000) * reject函數(shù)表示當(dāng)前Promise發(fā)生異常, 例子: setTimeout(()=>reject(Error("OMG!")), 1000) */ }) const p2 = p1.then( function fulfilled(val){ return val + 1 } , function rejected(err){ /*處理p1工廠函數(shù)中調(diào)用reject傳遞來(lái)的值*/ } ) const p3 = p2.then( function fulfilled(val){ return new Promise(function(resolve){setTimeout(()=>resolve(val+1), 10000)}) } , function rejected(err){ /*處理p1或p2調(diào)用reject或throw error的值*/ } ) p3.catch(function rejected(err){ /*處理p1或p2或p3調(diào)用reject或throw error的值*/ } )
Promises/A+中規(guī)定Promise狀態(tài)為pending(默認(rèn)值)、fulfilled或rejected,其中狀態(tài)僅能從pending->fulfilled或pending->rejected,并且可通過(guò)then和catch訂閱狀態(tài)變化事件。狀態(tài)變化事件的回調(diào)函數(shù)執(zhí)行結(jié)果會(huì)影響Promise鏈中下一個(gè)Promise實(shí)例的狀態(tài)。另外在觸發(fā)Promise狀態(tài)變化時(shí)是可以攜帶附加信息的,并且該附加信息將沿著Promise鏈被一直傳遞下去直到被某個(gè)Promise的事件回調(diào)函數(shù)接收為止。而且Promise還提供Promise.all和Promise.race兩個(gè)幫助方法來(lái)實(shí)現(xiàn)與或的邏輯關(guān)系,提供Promsie.resolve來(lái)將thenable對(duì)象轉(zhuǎn)換為Promise對(duì)象。
API:
new Promise(function(resolve, reject){}), 帶工廠函數(shù)的構(gòu)造函數(shù)
Promise.prototype.then(fulfilled()=>{}, rejected()=>{}),訂閱Promise實(shí)例狀態(tài)從pending到fulfilled,和從pending到rejected的變化
Promise.prototype.catch(rejected()=>{}),訂閱Promise實(shí)例狀態(tài)從pending到rejected的變化
Promise.resolve(val), 生成一個(gè)狀態(tài)為fulfilled的Promise實(shí)例
Promise.reject(val), 生成一個(gè)狀態(tài)為rejected的Promise實(shí)例
Promise.all(array), 生成一個(gè)Promise實(shí)例,當(dāng)array中所有Promise實(shí)例狀態(tài)均為fulfilled時(shí),該P(yáng)romise實(shí)例的狀態(tài)將從pending轉(zhuǎn)換為fulfilled,若array中某個(gè)Promise實(shí)例的狀態(tài)為rejected,則該實(shí)例的狀態(tài)將從pending轉(zhuǎn)換為rejected.
Promise.race(array), 生成一個(gè)Promise實(shí)例,當(dāng)array中某個(gè)Promise實(shí)例狀態(tài)發(fā)生轉(zhuǎn)換,那么該P(yáng)romise實(shí)例也隨之轉(zhuǎn)
const doAsyncIO = value => resolve => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定義任務(wù) */ const a = v番茄 => new Promise(doAsyncIO("番茄塊")) const b = v雞蛋 => new Promise(doAsyncIO("蛋液")) const c = v蛋液 => new Promise(doAsyncIO("半熟的雞蛋")) const d = v半熟的雞蛋 => new Promise(doAsyncIO("雞蛋塊")) const e = ([v番茄塊, v雞蛋塊]) => new Promise(doAsyncIO("番茄炒雞蛋")) /* 執(zhí)行任務(wù) */ Promise.all([ a("番茄"), b("雞蛋").then(c).then(d) ]).then(e) .catch(err=>{ console.log(err) })
最大特點(diǎn):獨(dú)立的可存儲(chǔ)的異步調(diào)用結(jié)果
其他特點(diǎn):fulfilled和rejected函數(shù)異步執(zhí)行
jQuery作為前端必備工具,也為我們提供類似與Promise的工具,那就是jQuery.Deffered
const deffered = $.getJSON("dummy.js") deffered.then(function(val1){ console.log(val1) return !val1 },function (err){ console.log(err) }).then(function(val2){ console.log(val2) })
但jQuery.Deferred并不是完整的Promise/A+的實(shí)現(xiàn)。
如:
jQuery1.8之前上述代碼val2的值與val1一樣,jQuery1.8及以后上述代碼val2的值就是!val1了。
fulfilled和rejected函數(shù)采用同步執(zhí)行
遺留問(wèn)題!
const a = () => Promise.resolve("a") const b = (v1) => Promise.resolve("b") const c = (v2, v1) => console.log(v1) a().then(b).then(c)真正的光明——Coroutine
?Coroutine中文就是協(xié)程,意思就是線程間采用協(xié)同合作的方式工作,而不是搶占式的方式工作。由于JS是單線程運(yùn)行的,所以這里的Coroutine就是一個(gè)可以部分執(zhí)行后退出,后續(xù)可在之前退出的地方繼續(xù)往下執(zhí)行的函數(shù).
function coroutine(){ yield console.log("c u later!") console.log("welcome guys!") }Generator Function
?其實(shí)就是迭代器,跟C#的IEnumrable、IEnumerator和Java的Iterable、Iterator一樣。
function* enumerable(){ yield 1 yield 2 } for (let num of enumerable()){ console.log(num) }
?現(xiàn)在我們將1,2替換為代碼
function *enumerable(msg){ console.log(msg) var msg1 = yield msg + " after " // 斷點(diǎn) console.log(msg1) var msg2 = yield msg1 + " after" // 斷點(diǎn) console.log(msg2 + " over") }
編譯器會(huì)將上述代碼轉(zhuǎn)換成
const enumerable = function(msg){ var state = -1 return { next: function(val){ switch(++state){ case 0: console.log(msg + " after") break case 1: var msg1 = val console.log(msg1 + " after") break case 2: var msg2 = val console.log(msg2 + " over") break } } } }
通過(guò)調(diào)用next函數(shù)就可以從之前退出的地方繼續(xù)執(zhí)行了。(條件控制、循環(huán)、迭代、異常捕獲處理等就更復(fù)雜了)
其實(shí)Generator Function實(shí)質(zhì)上就是定義一個(gè)有限狀態(tài)機(jī),然后通過(guò)Generator Function實(shí)例的next,throw和return方法觸發(fā)狀態(tài)遷移。
next(val), 返回{value: val1, done: true|false}
throw(err),在上次執(zhí)行的位置拋出異常
return(val),狀態(tài)機(jī)的狀態(tài)遷移至終止態(tài),并返回{value: val, done: true}
現(xiàn)在我們用Gererator Function來(lái)做番茄炒蛋
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定義任務(wù) */ const a = v番茄 => new Promise(doAsyncIO("番茄塊")) const b = v雞蛋 => new Promise(doAsyncIO("蛋液")) const c = v蛋液 => new Promise(doAsyncIO("半熟的雞蛋")) const d = v半熟的雞蛋 => new Promise(doAsyncIO("雞蛋塊")) const e = (v番茄塊, v雞蛋塊) => new Promise(doAsyncIO("番茄炒雞蛋")) function* coroutineFunction(){ try{ var p番茄塊 = a("番茄") var v蛋液 = yield b("雞蛋") var v半熟的雞蛋 = yield c(v蛋液) var v雞蛋塊 = yield d(v半熟的雞蛋) var v番茄塊 = yield p番茄塊 var v番茄抄雞蛋 = yield e(v番茄塊, v雞蛋塊) } catch(e){ console.log(e.message) } } const coroutine = coroutineFunction() throwError = coroutine.throw.bind(coroutine) coroutine.next().value.then(function(v蛋液){ coroutine.next(v蛋液).then(function(v半熟的雞蛋){ coroutine.next(v半熟的雞蛋).then(function(v雞蛋塊){ coroutine.next().then(function(v番茄塊){ coroutine.next(v番茄塊).then(function(v番茄抄雞蛋){ coroutine.next(v番茄抄雞蛋) }, throwError) }, throwError) }, throwError) }, throwError) })
?悲催又回到Callback hell.但我們可以發(fā)現(xiàn)coroutineFunction其實(shí)是以同步代碼的風(fēng)格來(lái)定義任務(wù)間的執(zhí)行順序(狀態(tài)依賴)而已,執(zhí)行模塊在后面這個(gè)讓人頭痛的Callback hell那里,并且這個(gè)Callback Hell是根據(jù)coroutineFunction的內(nèi)容生成,像這種重復(fù)有意義的事情自然由機(jī)器幫我們處理最為恰當(dāng)了,于是我們引入個(gè)狀態(tài)管理器得到
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定義任務(wù) */ const a = v番茄 => new Promise(doAsyncIO("番茄塊")) const b = v雞蛋 => new Promise(doAsyncIO("蛋液")) const c = v蛋液 => new Promise(doAsyncIO("半熟的雞蛋")) const d = v半熟的雞蛋 => new Promise(doAsyncIO("雞蛋塊")) const e = (v番茄塊, v雞蛋塊) => new Promise(doAsyncIO("番茄炒雞蛋")) function* coroutineFunction(){ try{ var p番茄塊 = a("番茄") var v蛋液 = yield b("雞蛋") var v半熟的雞蛋 = yield c(v蛋液) var v雞蛋塊 = yield d(v半熟的雞蛋) var v番茄塊 = yield p番茄塊 var v番茄抄雞蛋 = yield e(v番茄塊, v雞蛋塊) } catch(e){ console.log(e.message) } } iPromise(coroutineFunction)
?舒爽多了!
async和awaitES7引入了async和await兩個(gè)關(guān)鍵字,Node.js7支持這兩貨。于是Coroutine寫(xiě)法就更酸爽了.
const doAsyncIO = value => (resolve) => setTimeout(()=>resolve(value), Math.random() * 1000) /* 定義任務(wù) */ const a = v番茄 => new Promise(doAsyncIO("番茄塊")) const b = v雞蛋 => new Promise(doAsyncIO("蛋液")) const c = v蛋液 => new Promise(doAsyncIO("半熟的雞蛋")) const d = v半熟的雞蛋 => new Promise(doAsyncIO("雞蛋塊")) const e = (v番茄塊, v雞蛋塊) => new Promise(doAsyncIO("番茄炒雞蛋")) async function coroutine(){ try{ var p番茄塊 = a("番茄") var v蛋液 = await b("雞蛋") var v半熟的雞蛋 = await c(v蛋液) var v雞蛋塊 = await d(v半熟的雞蛋) var v番茄塊 = await p番茄塊 var v番茄抄雞蛋 = await e(v番茄塊, v雞蛋塊) } catch(e){ console.log(e.message) } } coroutine()總結(jié)
到這里各位應(yīng)該會(huì)想“不就做個(gè)西紅柿炒雞蛋嗎,搞這么多,至于嗎?”。其實(shí)我的看法是
對(duì)于狀態(tài)依賴簡(jiǎn)單的情況下,callback的方式足矣;
對(duì)于狀態(tài)依賴復(fù)雜(譬如做個(gè)佛跳墻等大菜時(shí)),Promise或Coroutine顯然會(huì)讓代碼更簡(jiǎn)潔直觀,更容易測(cè)試因此bug更少,更容易維護(hù)因此更易被優(yōu)化。
我曾夢(mèng)想有一天所有瀏覽器都支持Promise,async和await,大家可以不明就里地寫(xiě)出coroutine,完美地處理異步調(diào)用的各種問(wèn)題。直到有一天知道世上又多了Rxjs這貨,不說(shuō)了繼續(xù)填坑去:)
尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明來(lái)自:http://www.cnblogs.com/fsjohn... ^_^肥仔John
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/81073.html
摘要:前言最近加入到新項(xiàng)目組負(fù)責(zé)前端技術(shù)預(yù)研和選型,一直偏向于以為代表的技術(shù)線,于是查閱各類資料想說(shuō)服老大向這方面靠,最后得到的結(jié)果是資料是英語(yǔ)無(wú)所謂,最重要是上符合要求,技術(shù)的事你說(shuō)了算。但當(dāng)我們需要?jiǎng)討B(tài)實(shí)例化元素時(shí),命令式則是最佳的選擇。 前言 ?最近加入到新項(xiàng)目組負(fù)責(zé)前端技術(shù)預(yù)研和選型,一直偏向于以Polymer為代表的WebComponent技術(shù)線,于是查閱各類資料想說(shuō)服老大向這方面...
摘要:明確各階段適合的操作用于初始化元素的狀態(tài)和設(shè)置事件監(jiān)聽(tīng),或者創(chuàng)建。事件類型轉(zhuǎn)換通過(guò)捕獲事件,然后通過(guò)發(fā)起事件來(lái)對(duì)事件類型進(jìn)行轉(zhuǎn)換,從而觸發(fā)更符合元素特征的事件類型。 前言 ?通過(guò)《WebComponent魔法堂:深究Custom Element 之 面向痛點(diǎn)編程》,我們明白到其實(shí)Custom Element并不是什么新東西,我們甚至可以在IE5.5上定義自己的alert元素。但這種簡(jiǎn)單...
摘要:不耽誤表單提交數(shù)據(jù)雖然我們無(wú)法看到的元素,但當(dāng)表單提交時(shí)依然會(huì)將隱藏的元素的值提交上去。讓元素在見(jiàn)面上不可視,但保留元素原來(lái)占有的位置。不過(guò)由于各瀏覽器實(shí)現(xiàn)效果均有出入,因此一般不會(huì)使用這個(gè)值。繼承父元素的值。 前言 ?還記得面試時(shí)被問(wèn)起請(qǐng)說(shuō)說(shuō)display:none和visibility:hidden的區(qū)別嗎?是不是回答完display:none不占用原來(lái)的位置,而visibilit...
摘要:我打算分成前端魔法堂異常不僅僅是和前端魔法堂調(diào)用棧,異常實(shí)例中的寶藏兩篇分別敘述內(nèi)置自定義異常類,捕獲運(yùn)行時(shí)異常語(yǔ)法異常網(wǎng)絡(luò)請(qǐng)求異常事件,什么是調(diào)用棧和如何獲取調(diào)用棧的相關(guān)信息。 前言 ?編程時(shí)我們往往拿到的是業(yè)務(wù)流程正確的業(yè)務(wù)說(shuō)明文檔或規(guī)范,但實(shí)際開(kāi)發(fā)中卻布滿荊棘和例外情況,而這些例外中包含業(yè)務(wù)用例的例外,也包含技術(shù)上的例外。對(duì)于業(yè)務(wù)用例的例外我們別無(wú)它法,必須要求實(shí)施人員與用戶共同...
摘要:一前言圖片上傳是一個(gè)普通不過(guò)的功能,而圖片預(yù)覽就是就是上傳功能中必不可少的子功能了。偶然從上找到純前端圖片預(yù)覽的相關(guān)資料,經(jīng)過(guò)整理后記錄下來(lái)以便日后查閱。類型為,表示在讀取文件時(shí)發(fā)生的錯(cuò)誤,只讀。 一、前言 圖片上傳是一個(gè)普通不過(guò)的功能,而圖片預(yù)覽就是就是上傳功能中必不可少的子功能了。在這之前,我曾經(jīng)通過(guò)訂閱input[type=file]元素的onchange事件,一旦更改路徑...
閱讀 2744·2021-10-26 09:50
閱讀 2385·2021-10-11 11:08
閱讀 2128·2019-08-30 15:53
閱讀 1906·2019-08-30 15:44
閱讀 2382·2019-08-28 18:12
閱讀 2520·2019-08-26 13:59
閱讀 2853·2019-08-26 12:19
閱讀 2752·2019-08-26 12:09