摘要:參考文章珠峰架構課墻裂推薦細說異步函數發(fā)展歷程異步編程謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發(fā),請不要吝嗇你的贊和,您的肯定是我前進的最大動力。
知其然知其所以然,首先了解三個概念:
1.什么是同步?
所謂同步,就是在發(fā)出一個"調用"時,在沒有得到結果之前,該“調用”就不返回。但是一旦調用返回,就得到返回值了。換句話說,就是由“調用者”主動等待這個“調用”的結果。此調用執(zhí)行完之前,阻塞之后的代碼執(zhí)行。
2.什么是異步?
"調用"在發(fā)出之后,這個調用就直接返回了,所以沒有返回結果。換句話說,當一個異步過程調用發(fā)出后,調用者不會立刻得到結果。而是在"調用"發(fā)出后,"被調用者"通過狀態(tài)、通知來通知調用者,或通過回調函數處理這個調用。異步調用發(fā)出后,不影響后面代碼的執(zhí)行。
3.JavaScript 中為什么需要異步?
首先我們知道JavaScript是單線程的(即使新增了webworker,但是本質上JS還是單線程)。同步代碼意味著什么呢?意味著有可能會阻塞,當我們有一個任務需要時間較長時,如果使用同步方式,那么就會阻塞之后的代碼執(zhí)行。而異步則不會,我們不會等待異步代碼的之后,繼續(xù)執(zhí)行異步任務之后的代碼。
更多優(yōu)質文章可戳: https://github.com/YvetteLau/...
概念了解完了,我們就要進入今天的正題了。首先大家思考一下:平時在工作中,主要使用了哪些異步解決方案,這些異步方案有什么優(yōu)缺點?
異步最早的解決方案是回調函數,如事件的回調,setInterval/setTimeout中的回調。但是回調函數有一個很常見的問題,就是回調地獄的問題(稍后會舉例說明);
為了解決回調地獄的問題,社區(qū)提出了Promise解決方案,ES6將其寫進了語言標準。Promise一定程度上解決了回調地獄的問題,但是Promise也存在一些問題,如錯誤不能被try catch,而且使用Promise的鏈式調用,其實并沒有從根本上解決回調地獄的問題,只是換了一種寫法。
ES6中引入 Generator 函數,Generator是一種異步編程解決方案,Generator 函數是協(xié)程在 ES6 的實現,最大特點就是可以交出函數的執(zhí)行權,Generator 函數可以看出是異步任務的容器,需要暫停的地方,都用yield語句注明。但是 Generator 使用起來較為復雜。
ES7又提出了新的異步解決方案:async/await,async是 Generator 函數的語法糖,async/await 使得異步代碼看起來像同步代碼,異步編程發(fā)展的目標就是讓異步邏輯的代碼看起來像同步一樣。
回調函數 ---> Promise ---> Generator ---> async/await.1.回調函數: callback
//node讀取文件 fs.readFile(xxx, "utf-8", function(err, data) { //code });
回調函數的使用場景(包括但不限于):
事件回調
Node API
setTimeout/setInterval中的回調函數
ajax 請求
回調函數的優(yōu)點: 簡單。回調函數的缺點:
異步回調嵌套會導致代碼難以維護,并且不方便統(tǒng)一處理錯誤,不能 try catch 和 回調地獄(如先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C...)。
fs.readFile(A, "utf-8", function(err, data) { fs.readFile(B, "utf-8", function(err, data) { fs.readFile(C, "utf-8", function(err, data) { fs.readFile(D, "utf-8", function(err, data) { //.... }); }); }); });2.Promise
Promise 一定程度上解決了回調地獄的問題,Promise 最早由社區(qū)提出和實現,ES6 將其寫進了語言標準,統(tǒng)一了用法,原生提供了Promise對象。
那么我們看看Promise是如何解決回調地獄問題的,仍然以上文的readFile 為例(先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C)。
function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, "utf8", (err, data) => { if(err) reject(err); resolve(data); }); }); } read(A).then(data => { return read(B); }).then(data => { return read(C); }).then(data => { return read(D); }).catch(reason => { console.log(reason); });
Promise 的優(yōu)點:
一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結果
可以將異步操作以同步操作的流程表達出來,避免了層層嵌套的回調函數
缺點:
無法取消 Promise
當處于pending狀態(tài)時,無法得知目前進展到哪一個階段
錯誤不能被 try catch
假設有這樣一個需求:讀取A,B,C三個文件內容,都讀取成功后,再輸出最終的結果。在Promise之前,我們一般可以借助發(fā)布訂閱模式去實現:
let pubsub = { arry: [], emit() { this.arry.forEach(fn => fn()); }, on(fn) { this.arry.push(fn); } } let data = []; pubsub.on(() => { if(data.length === 3) { console.log(data); } }); fs.readFile(A, "utf-8", (err, value) => { data.push(value); pubsub.emit(); }); fs.readFile(B, "utf-8", (err, value) => { data.push(value); pubsub.emit(); }); fs.readFile(C, "utf-8", (err, value) => { data.push(value); pubsub.emit(); });
Promise給我們提供了 Promise.all 的方法,對于這個需求,我們可以使用 Promise.all 來實現。
/** * 將 fs.readFile 包裝成promise接口 */ function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, "utf8", (err, data) => { if(err) reject(err); resolve(data); }); }); } /** * 使用 Promise * * 通過 Promise.all 可以實現多個異步并行執(zhí)行,同一時刻獲取最終結果的問題 */ Promise.all([ read(A), read(B), read(C) ]).then(data => { console.log(data); }).catch(err => console.log(err));
可執(zhí)行代碼可戳: https://github.com/YvetteLau/...
3.GeneratorGenerator 函數是 ES6 提供的一種異步編程解決方案,整個 Generator 函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用 yield 語句注明。
Generator 函數一般配合 yield 或 Promise 使用。Generator函數返回的是迭代器。對生成器和迭代器不了解的同學,請自行補習下基礎。下面我們看一下 Generator 的簡單使用:
function* gen() { let a = yield 111; console.log(a); let b = yield 222; console.log(b); let c = yield 333; console.log(c); let d = yield 444; console.log(d); } let t = gen(); //next方法可以帶一個參數,該參數就會被當作上一個yield表達式的返回值 t.next(1); //第一次調用next函數時,傳遞的參數無效 t.next(2); //a輸出2; t.next(3); //b輸出3; t.next(4); //c輸出4; t.next(5); //d輸出5;
為了讓大家更好的理解上面代碼是如何執(zhí)行的,我畫了一張圖,分別對應每一次的next方法調用:
仍然以上文的 readFile (先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C)為例,使用 Generator + co庫來實現:
const fs = require("fs"); const co = require("co"); const bluebird = require("bluebird"); const readFile = bluebird.promisify(fs.readFile); function* read() { yield readFile(A, "utf-8"); yield readFile(B, "utf-8"); yield readFile(C, "utf-8"); //.... } co(read()).then(data => { //code }).catch(err => { //code });
Generator的缺點大約不用我說了,除非是找虐,不然一般不會直接使用 Generator 來解決異步的(當然也不排除是因為我不熟練)~~~
不使用co庫,如何實現?能否自己寫一個最簡的 my_co,有助于理解 async/await 的實現原理 ?請戳: https://github.com/YvetteLau/...
PS: 如果你還不太了解 Generator/yield,建議閱讀ES6相關文檔。
4.async/awaitES7中引入了 async/await 概念。async 其實是一個語法糖,它的實現就是將 Generator函數和自動執(zhí)行器(co),包裝在一個函數中。
async/await 的優(yōu)點是代碼清晰,不用像 Promise 寫很多 then 鏈,就可以處理回調地獄的問題。并且錯誤可以被try catch。
仍然以上文的readFile (先讀取A文本內容,再根據A文本內容讀取B再根據B的內容讀取C) 為例,使用 async/await 來實現:
const fs = require("fs"); const bluebird = require("bluebird"); const readFile = bluebird.promisify(fs.readFile); async function read() { await readFile(A, "utf-8"); await readFile(B, "utf-8"); await readFile(C, "utf-8"); //code } read().then((data) => { //code }).catch(err => { //code });
使用 async/await 實現此需求:讀取A,B,C三個文件內容,都讀取成功后,再輸出最終的結果。
function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, "utf8", (err, data) => { if(err) reject(err); resolve(data); }); }); } async function readAsync() { let data = await Promise.all([ read(A), read(B), read(C) ]); return data; } readAsync().then(data => { console.log(data); });
所以JS的異步發(fā)展史,可以認為是從 callback -> promise -> generator -> async/await。async/await 使得異步代碼看起來像同步代碼,異步編程發(fā)展的目標就是讓異步邏輯的代碼看起來像同步一樣。
因本人水平有限,文中內容未必百分百正確,如有不對的地方,請給我留言,謝謝。
邀請你加入 Step-By-Step 項目
不積跬步無以至千里。 我是公眾號【前端宇宙】作者劉小夕,我將和大家一起一步一個腳印,向前端專家邁進。
Step-By-Step
每個工作日我會發(fā)布一個前端相關的問題(目的是為了切實掌握相關的知識點),歡迎在 Issue 區(qū)留下你的答案。
節(jié)假日不會發(fā)布任何問題,希望大家能夠利用節(jié)假日回顧一周所學。每周末我會進行一次匯總(整理出最優(yōu)答案),以便大家回顧。
參考文章:
[1] 珠峰架構課(墻裂推薦)
[2] 細說JavaScript異步函數發(fā)展歷程
[3] ES6 Promise
[4] ES6 Generator
[5] ES6 async
[6] JavaScript異步編程
謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發(fā),請不要吝嗇你的贊和Star,您的肯定是我前進的最大動力。https://github.com/YvetteLau/...
關注小姐姐的公眾號,加入交流群。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/109732.html
摘要:換句話說,當一個異步過程調用發(fā)出后,調用者不會立刻得到結果。參考文章珠峰架構課墻裂推薦細說異步函數發(fā)展歷程異步編程謝謝各位小伙伴愿意花費寶貴的時間閱讀本文,如果本文給了您一點幫助或者是啟發(fā),請不要吝嗇你的贊和,您的肯定是我前進的最大動力。知其然知其所以然,首先了解三個概念: 1.什么是同步? 所謂同步,就是在發(fā)出一個調用時,在沒有得到結果之前,該調用就不返回。但是一旦調用返回,就得到返回值了...
摘要:參考精讀模塊化發(fā)展模塊化七日談前端模塊化開發(fā)那點歷史本文先發(fā)布于我的個人博客模塊化開發(fā)的演進歷程,后續(xù)如有更新,可以查看原文。 Brendan Eich用了10天就創(chuàng)造了JavaScript,因為當時的需求定位,導致了在設計之初,在語言層就不包含很多高級語言的特性,其中就包括模塊這個特性,但是經過了這么多年的發(fā)展,如今對JavaScript的需求已經遠遠超出了Brendan Eich的...
摘要:本文從入手,系統(tǒng)的回顧的異步機制及發(fā)展歷程。需要提醒的是,文本沒有討論的異步機制。本文是專題系列文章之一,后續(xù)會有更多專題推出地址持續(xù)更新博客地址文章排版真的很漂亮如果覺得對你有幫助,歡迎來點或者來我的博客親口告訴我本文從Event Loop、Promise、Generator、async await入手,系統(tǒng)的回顧 JavaScript 的異步機制及發(fā)展歷程。 需要提醒的是,文本沒有討論 ...
摘要:的翻譯文檔由的維護很多人說,阮老師已經有一本關于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。 JavaScript Promise 迷你書(中文版) 超詳細介紹promise的gitbook,看完再不會promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:前端的發(fā)展歷程什么是前端前端針對瀏覽器的開發(fā),代碼在瀏覽器運行后端針對服務器的開發(fā),代碼在服務器運行前端三劍客超文本標記語言是構成世界的基石。 前端的發(fā)展歷程 什么是前端 前端:針對瀏覽器的開發(fā),代碼在瀏覽器運行 后端:針對服務器的開發(fā),代碼在服務器運行 前端三劍客 HTML CSS JavaScript HTML HTML(超文本標記語言——HyperText Markup ...
閱讀 3479·2021-11-25 09:43
閱讀 2630·2021-09-22 15:54
閱讀 607·2019-08-30 15:55
閱讀 984·2019-08-30 15:55
閱讀 2010·2019-08-30 15:55
閱讀 1754·2019-08-30 15:53
閱讀 3479·2019-08-30 15:52
閱讀 2049·2019-08-30 12:55