摘要:異步過程控制了解異步的意義之后,我們來對比目前主流幾種異步過程控制方法,探討一下異步編程的最佳實踐。結語希望本文對大家有點幫助,能更深刻的理解異步編程,能寫出更優雅更高效的代碼。
同步和異步(Synchronous and Asynchronous)
了解javascript的同學想必對同步和異步的概念應該都很熟悉了,如果還有不熟悉的同學,我這里舉個形象的例子,比如我們早上起床后要干三件事:燒水、洗臉、吃早飯,同步相當于我們先燒水,水燒開了再洗臉,洗完臉再吃早飯,三件事順序執行,一件干完了再干下一件;而異步相當于我們在燒水的同時吃早飯(不洗臉就吃早飯不太衛生),吃完早飯再洗臉。顯然異步比同步更加高效,省去了很多等待的時間,同步過程的執行時間取決于所有行為的總和,而異步過程的執行時間只取決于最長的那個行為,如下圖所示:
由于Javascript是單線程的,同時只能處理一件事,在上面的例子中這個單線程就是“我”,比如我不能同時洗臉和吃早飯一樣。所以為了讓執行效率提高,我們要盡量讓這個線程一直處于忙碌狀態而不是閑置狀態,就像我們不用干等燒水,可以同時去做其他事情,而燒水由系統的其他線程去處理(該線程不屬于Javascript)。在計算機的世界中,很多I/O密集型的操作是需要等待的,比如網絡請求、文件讀寫等,所以異步方法在處理這些操作會更加得心應手。
異步過程控制了解異步的意義之后,我們來對比目前主流幾種異步過程控制方法,探討一下異步編程的最佳實踐。
1. Callback 誤區首先callback和異步沒有必然聯系,callback本質就是類型為function的函數參數,對于該callback是同步還是異步執行則取決于函數本身。雖然callback常用于異步方法的回調,但其實有不少同步方法也可以傳入callback,比如最常見的數組的forEach方法:
var arr = [1, 2, 3]; arr.forEach(function (val) { console.log(val); }); console.log("finish"); // 打印結果:1,2,3,finish
類似的還有數組的map, filter, reduce等很多方法。
異步Callback常見的異步callback如setTimeout中的回調:
setTimeout(function () { console.log("time"s up"); }, 1000); console.log("finish"); // 打印結果:finish, time"s up
如果我們將延遲時間改為0,打印結果仍將是finish, time"s up,因為異步callback會等函數中的同步方法都執行完成后再執行。
Callback Hell在實際項目中我們經常會遇到這樣的問題:下一步操作依賴于上一步操作的結果,上一步操作又依賴于上上步操作,而每一步操作都是異步的。。這樣遞進的層級多了會形成很多層callback嵌套,導致代碼可讀性和可維護性變的很差,形成所謂的Callback Hell,類似這樣:
step1(param, function (result1) { step2(result1, function (result2) { step3(result2, function (result3) { step4(result3, function (result4) { done(result4); }) }) }) })
當然在不放棄使用callback的前提下,上面的代碼還是有優化空間的,我們可以將它重新組織一下:
step1(param, callbac1); function callback1(result1){ step2(result1, callback2); } function callback2(result2){ step3(result2, callback3); } function callback3(result3){ step4(result3, callback4); } function callback4(result4){ done(result4); }
相當于將Callback Hell的橫向深度轉化為代碼的縱向高度,變得更接近于我們習慣的由上到下的同步調用, 復雜度沒有變,只是看起來更清晰了,缺點就是要定義額外的函數、變量。將這一思想進一步延伸就有了下面的Promise。
2. PromisePromise中文譯為“承諾”,在Javascript中是一個抽象的概念,代表當前沒有實現,但未來的某個時間點會(也可能不會)實現的一件事。舉個實例化的例子:早上燒水,我給你一個承諾(Promise),十分鐘后水能燒開,如果一切正常,10分鐘之后水確實能燒開,代表這個promise兌現了(fullfilled),但是如果中途停電了,10分鐘水沒燒開,那這個promise兌現失敗(rejected)。用代碼可以表示為:
const boilWaterInTenMins = new Promise(function (resolve, reject) { boiler.work(function (timeSpent) { if (timeSpent <= 10) { resolve(); } else { reject(); } }); });兼容性
如果想提高瀏覽器對Promise的兼容性可以使用babel或者第三方的實現(參考 github awesome promise)
Promise Chaining我們再來看Promise對于異步過程控制有怎樣的提升,還基于上面Callback Hell的例子,如果用Promise實現會如何呢?
首先我們需要將step1 ~ done 的函數用Promise實現(即返回一個Promise),然后進行一連串的鏈式調用就可以了:
stepOne(param) .then((result1) => { return step2(result1) }) .then((result2) => { return step3(result2) }) .then((result3) => { return step4(result3) }) .then((result4) => { return done(result4) }) .catch(err => handleError(err));
是不是簡單很多!
Async/Await如果你不太習慣Promise的調用方式,那我們可以用async/await將其轉化成更接近同步調用的方式:
async function main() { try { var result1 = await step1(param); var result2 = await step2(result1); var result3 = await step3(result2); var result4 = await step4(result3); done(result4); } catch (err) { handleError(err); } } main();3. Generator
Generator是一個更加抽象的概念,要弄懂什么是Generator首先要理解另外幾個概念Iterable Protocol(可迭代協議),Iterator Protocol(迭代器協議)和 Iterator(迭代器)。
Iterable ProtocolIterable Protocol 的特點可以概括為:
用于定義javascript對象的迭代行為
對象本身或者原型鏈上需要有一個名為Symbol.iterator的方法
該方法不接收任何參數,且返回一個Iterator
Iterable的對象可以使用for...of遍歷
Javascript Array就實現了Iterable Protocol,除了常規的取值方式,我們也可以利用array的Symbol.iterator:
var arr = [1, 2, 3]; var iterator = arr[Symbol.iterator](); iterator.next(); // {value: 1, done: false}
我們也可以修改Array默認的迭代方式,比如返回兩倍的值:
Array.prototype[Symbol.iterator] = function () { var nextIndex = 0; var self = this; return { next: function () { return nextIndex < self.length ? { value: self[nextIndex++] * 2, done: false } : { done: true } } }; } for(let el of [1, 2, 3]){ console.log(el); } // 輸出:2,4,6Iterator Protocol
Iterator Protocol 的特點可以概括為:
一種產生一個序列值(有限或無限)的標準方式
實現一個next方法
next方法返回的對象為 {value: any, done: boolean}
value為返回值,done為true時value可以省略
done為true表示迭代結束,此時value表示最終返回值
done為false,則可以繼續迭代,產生下一個值
Iterator顯然Iterator就是實現了Iterator Protocol的對象。
Generator理解上面幾個概念后,理解Generator就簡單多了,generator的特點可概括為:
同時實現Iterable Protocol和Iterator Protocol,所以Genrator即是一個iterable的對象又是一個iterator
Generator由 generator function 生成
最簡單的generator function比如:
function* gen() { var x = yield 5 + 6; } var myGen = gen(); // myGen 就是一個generator
我們可以調用next方法來獲得yield表達式的值:
myGen.next(); // { value: 11, done: false }
但此時x并沒有被賦值,可以想象成javascript執行完 yield 5 + 6 就停住了,為了繼續執行賦值操作我們需要再次調用next,并將得到的值回傳:
function* gen() { var x = yield 5 + 6; console.log(x); // 11 } var myGen = gen(); console.log(myGen.next()); // { value: 11, done: false } console.log(myGen.next(11)); // { value: undefined, done: true }
說了這么多,generator和異步到底有什么關系呢?我們來看Promise + Generator 實現的異步控制(step1 ~ done 返回Promise):
genWrap(function* () { var result1 = yield step1(param); var result2 = yield step2(result1); var result3 = yield step3(result2); var result4 = yield step4(result3); var result5 = yield done(result4); }); function genWrap(genFunc) { var generator = genFunc(); function handle(yielded) { if (!yielded.done) { yielded.value.then(function (result) { return handle(generator.next(result)); }); } } return handle(generator.next()); }
和async/await類似,這種實現也將異步方法轉化成了同步的寫法,實際上這就是 ES7中async/await的實現原理(將genWrap替換為async,將yield替換成await)。
結語希望本文對大家有點幫助,能更深刻的理解javascript異步編程,能寫出更優雅更高效的代碼。有錯誤歡迎指正。新年快樂!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101542.html
摘要:接下來我們看下三類異步編程的實現。事件監聽事件發布訂閱事件監聽是一種非常常見的異步編程模式,它是一種典型的邏輯分離方式,對代碼解耦很有用處。 一、 一道面試題 前段時間面試,考察比較多的是js異步編程方面的相關知識點,如今,正好輪到自己分享技術,所以想把js異步編程學習下,做個總結。下面這個demo 概括了大多數面試過程中遇到的問題: for(var i = 0; i < 3; i++...
摘要:的異步完成整個異步環節的有事件循環觀察者請求對象以及線程池。執行回調組裝好請求對象送入線程池等待執行,實際上是完成了異步的第一部分,回調通知是第二部分。異步編程是首個將異步大規模帶到應用層面的平臺。 showImg(https://segmentfault.com/img/remote/1460000011303472); 本文首發在個人博客:http://muyunyun.cn/po...
摘要:接下來,我們一起來看看中的異步編程,具體有哪幾種。實現異步編程的方法一回調函數上面不止一次提到了回調函數。它是異步編程中,最基本的方法。四對象接下來,我們聊聊與相關的異步編程方法,對象。 showImg(https://segmentfault.com/img/bVbneWy?w=1600&h=1200); 前言 最近,小伙伴S 問了我一段代碼: const funB = (value...
摘要:異步問題回調地獄首先,我們來看下異步編程中最常見的一種問題,便是回調地獄。同時使用也是異步編程最基礎和核心的一種解決思路。基于,目前也被廣泛運用,其是異步編程的一種解決方案,比傳統的回調函數解決方案更合理和強大。 關于 微信公眾號:前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實際編碼中,我們經常會遇到Javascript代碼異步執行的場景...
摘要:因為瀏覽器環境里是單線程的,所以異步編程在前端領域尤為重要。除此之外,它還有兩個特性,使它可以作為異步編程的完整解決方案函數體內外的數據交換和錯誤處理機制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我們日常編碼中,需要異步的場景很多,比如讀取文件內容、獲取遠程數據、發送數據到服務端等。因為瀏覽器環境里Javascript是單線程的,...
閱讀 1655·2021-09-26 09:55
閱讀 5248·2021-09-22 15:40
閱讀 2013·2019-08-30 15:53
閱讀 1497·2019-08-30 11:15
閱讀 1714·2019-08-29 15:41
閱讀 1869·2019-08-28 18:13
閱讀 3146·2019-08-26 12:00
閱讀 1668·2019-08-26 10:30