摘要:異步流程管理說白了就是為了解決回調(diào)地獄的問題。對象代表一個異步操作,有三種狀態(tài)進行中已成功和已失敗。如果改變已經(jīng)發(fā)生了,你再對對象添加回調(diào)函數(shù),也會立即得到這個結果。執(zhí)行函數(shù)后返回的是一個遍歷器對象,可以依次遍歷函數(shù)內(nèi)部的每一個狀態(tài)。
javascript -- 深度解析異步解決方案
高級語言層出不窮, 然而唯 js 鶴立雞群, 這要說道js的設計理念, js天生為異步而生, 正如布道者樸靈在 node深入淺出--(有興趣的可以讀一下, 很有意思^_^) , 異步很早就存在于操作系統(tǒng)的底層, 意外的是,在絕大多數(shù)高級編程語言中,異步并不多見,疑似被屏蔽了一搬. 造成這個現(xiàn)象的原因或許令人驚訝, 程序員不太適合通過異步來實現(xiàn)進行程序設計 ^_^.Javascript異步編程解決方案歷史與方法異步的理念是很好的, 然而在程序員編程過程中確實會出現(xiàn)一些問題, 并不是這種理念不容以讓人接受, 而是當有大量的異步操作時會讓你的代碼可讀性降低, 其中回調(diào)函數(shù)異步編程容易產(chǎn)生毀掉陷阱, 即 callback hell--(不要急, 后面會詳細講解)
然而 js 社區(qū)從為停止其腳步, 最新的 ES7 所推出的 async/await 終極異步解決方案, 說終極可能有所不嚴禁, 然而它確實已經(jīng)完全將原來通過模塊侵入式的異步編程解脫出來, 可以讓程序員以接近傳統(tǒng)意義上的函數(shù)調(diào)用實現(xiàn)異步編程, 這是 js 里程碑式變革中極其重要的一部分.
ES 6以前:
回調(diào)函數(shù)
回調(diào)函數(shù)是最原始的異步編程方案, 上篇文章已經(jīng)講述, 這里不再累贅, 這里給出傳送門 回調(diào)函數(shù)之美 然而如果業(yè)務邏輯過多時, 回調(diào)函數(shù)會產(chǎn)生深層嵌套, 對程序員極不友好,
如下代碼所示有一個業(yè)務邏輯, 需要對a, b, c三個文件一次讀取
var fs = require("fs"); fs.readFile("./a.txt", function(err1, data1) { fs.readFile("./b.txt", function(err2, data2) { fs.writeFile("./ab.txt", data1 + data2, function(err) { console.log("read and write done!"); }); }); });
三個異步函數(shù)嵌套看起來挺簡單的, 這里知識簡單假設, 拋磚引玉, 如果有5個,10個甚至更多的異步函數(shù)要順序執(zhí)行,那要嵌套(大家都不喜歡身材橫著長吧哈哈)說實話相當恐怖,代碼會變得異常難讀,難調(diào)試,難維護。這就是所謂的回調(diào)地獄或者callback hell。正是為了解決這個問題,才有了后面兩節(jié)要講的內(nèi)容,用promise或generator進行異步流程管理。異步流程管理說白了就是為了解決回調(diào)地獄的問題。所以說任何事情都有兩面性,異步編程有它獨特的優(yōu)勢,卻也同時遇到了同步編程根本不會有的代碼組織難題。
事件監(jiān)聽(事件發(fā)布/訂閱)
事件監(jiān)聽模式是一種廣泛應用于異步編程的模式, 是回調(diào)函數(shù)的事件化,即發(fā)布/訂閱模式,
var util = require("util"); var events = require("events"); function Stream() { events.EventEmitter.call(this); } util.inherits(Stream, events.EventEmitter) let got = new Stream(); got.on("done", function (params) { console.log(params); }); got.on("done", function (params) { console.log("QWER"); }); got.emit("done", "diyige"); console.log("-----------------"); var emitter = new events.EventEmitter(); emitter.on("done", function (params) { console.log(params); }); emitter.on("done", function (params) { console.log("ZXCV"); }); emitter.emit("done", "dierge"); // diyige // QWER // dierge // ZXCV
Promise對象
Promise 是異步編程的一種解決方案,它是比傳統(tǒng)的解決方案——回調(diào)函數(shù)和事件——更合理和更強大, 它的目的是替換以前回調(diào)函數(shù)的比不編程方案, 也是后續(xù)介紹的異步解決方案的基礎, 它由社區(qū)最早提出和實現(xiàn),ES6 將其寫進了語言標準,統(tǒng)一了用法,原生提供了Promise對象, 現(xiàn)在的 js庫幾乎都支持這種異步方案
promise對象有以下特點
對象的狀態(tài)不受外界影響。Promise對象代表一個異步操作,有三種狀態(tài):pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態(tài),任何其他操作都無法改變這個狀態(tài)。這也是Promise這個名字的由來,它的英語意思就是“承諾”,表示其他手段無法改變
一旦狀態(tài)改變,就不會再變,任何時候都可以得到這個結果。Promise對象的狀態(tài)改變,只有兩種可能:從pending變?yōu)閒ulfilled和從pending變?yōu)閞ejected。只要這兩種情況發(fā)生,狀態(tài)就凝固了,不會再變了,會一直保持這個結果,這時就稱為 resolved(已定型)。如果改變已經(jīng)發(fā)生了,你再對Promise對象添加回調(diào)函數(shù),也會立即得到這個結果。這與事件(Event)完全不同,事件的特點是,如果你錯過了它,再去監(jiān)聽,是得不到結果的。
下面為單個promise對象應用方法
var promise = new Promise(function(resolve,reject){ // ... some code if(/* 異步操作成功 */){ resolve(value); }else{ reject(error); } });
通常用promise 的時候我們一般把它相應的業(yè)務包裝起來下圖所示模擬了一個讀取文件的異步
promise 函數(shù),
var readFile = function (params) { return new Promise(function(resolve, reject){ setTimeout(function(){ resolve(params); }, 2000); }); } readFile("file1").then(function (data) { console.log(data); return readFile("file2") }).then(function (data) { console.log(data); return readFile("file3") }).then(function (data) { console.log(data); return readFile("file4") }).then(function (data) { console.log(data); return readFile("file5") }).then(function (data) { console.log(data); }) //file1 //file2 //file3 //file4 //file5
流程控制庫
還有一種需要手工調(diào)用采能夠處理后續(xù)任務的, 在這里只簡單介紹一種, 我們稱之為尾觸發(fā), 常用的關鍵字為 next , 為什么要講到它是因為它是 node 神級框架 express中采用的模式, 這里可能要涉及一些后端node的內(nèi)容
在 node 搭建服務器時需要面向 切面編程 ,這就需要各種各樣的中間件
var app = connect(); // Middleware app.use(connect.staticCache()); app.use(connect.static(__dirname + "/public")); app.use(connect.cookieParser()); app.use(connect.session()); app.use(connect.query()); app.use(connect.bodyParser()); app.use(connect.csrf()); app.listen(3001);
在通過 use() 方法監(jiān)聽好一系列中間件后, 監(jiān)聽端口上的請求, 中間件采用的是尾觸發(fā)的機制, 下面是個一個簡單的中間件
function (req, res, next) { // express中間件 }
每個中間件傳遞請求對象, 響應對象, 和尾觸發(fā)函數(shù), 通過隊列形成一個處理流, 如下圖
中間件機制使得在處理網(wǎng)絡請求時, 可以像面向切面編程一樣進行過濾, 驗證, 日志等功能.
ES 6:
Generator函數(shù)(協(xié)程coroutine)
Generator 函數(shù)有多種理解角度。語法上,Generator 函數(shù)是一個狀態(tài)機,封裝了多個內(nèi)部狀態(tài)。
執(zhí)行 Generator 函數(shù)會返回一個遍歷器對象,也就是說,Generator 函數(shù)除了狀態(tài)機,還是一個遍歷器對象生成函數(shù)。執(zhí)行函數(shù)后返回的是一個遍歷器對象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個狀態(tài)。
function* helloWorldGenerator() { yield "hello"; yield "world"; return "ending"; } var hw = helloWorldGenerator(); hw.next() // { value: "hello", done: false } hw.next() // { value: "world", done: false } hw.next() // { value: "ending", done: true } hw.next() // { value: undefined, done: true }
下一步,必須調(diào)用遍歷器對象的next方法,使得指針移向下一個狀態(tài)。也就是說,每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來的地方開始執(zhí)行,直到遇到下一個yield表達式(或return語句)為止。換言之,Generator 函數(shù)是分段執(zhí)行的,yield表達式是暫停執(zhí)行的標記,而next方法可以恢復執(zhí)行。
基于 Promise 對象的自動執(zhí)行
generater/yield函數(shù)還無法真正解決異步方案的問題, 需要配合額外的執(zhí)行模塊 如 TJ Holowaychuk 的 co 模塊, 在這里用promise模塊進行generater函數(shù)的自動執(zhí)行;
var fs = require("fs"); var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; var gen = function* (){ var f1 = yield readFile("/etc/fstab"); var f2 = yield readFile("/etc/shells"); console.log(f1.toString()); console.log(f2.toString()); }; /***************************************** var g = gen(); g.next().value.then(function(data){ g.next(data).value.then(function(data){ g.next(data); }); }); *****************************************/ // 自動執(zhí)行函數(shù) function run(gen){ var g = gen(); function next(data){ var result = g.next(data); if (result.done) return result.value; result.value.then(function(data){ next(data); }); } next(); } run(gen);
ES 7:
async/await
終于來到了我們夢寐以求的的"終極"異步解決方案, 或許你有些失望, 當然這種失望是async/await 僅僅是語法糖, async/await 就是 generater/yield/promise + 自動執(zhí)行模塊的封裝.相對于前輩 async 函數(shù)可以自動執(zhí)行 并且 await 關鍵字后面則只能帶promise隊形--這里注意 await 后面支持其他數(shù)據(jù)類型, 但是底層也會將其轉化為promise對象
async函數(shù)對 Generator 函數(shù)的改進,體現(xiàn)在以下四點。
內(nèi)置執(zhí)行器。
Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了co模塊,而async函數(shù)自帶執(zhí)行器,這完全不像 Generator 函數(shù),需要調(diào)用next方法,或者用co模塊,才能真正執(zhí)行,得到最后結果。
更好的語義。
async和await,比起星號和yield,語義更清楚了。async表示函數(shù)里有異步操作,await表示緊跟在后面的表達式需要等待結果。
更廣的適用性。
co模塊約定,yield命令后面只能是 Thunk 函數(shù)或 Promise 對象,而async函數(shù)的await命令后面,可以是 Promise 對象和原始類型的值(數(shù)值、字符串和布爾值,但這時等同于同步操作)
返回值是 Promise。
async函數(shù)的返回值是 Promise 對象,這比 Generator 函數(shù)的返回值是 Iterator 對象方便多了。你可以用then方法指定下一步的操作。進一步說,async函數(shù)完全可以看作多個異步操作,包裝成的一個 Promise 對象,而await命令就是內(nèi)部then命令的語法糖。
function name(params) { return new Promise(function (resolve, reject) { setTimeout(() => { resolve(params) }, 3000); }); } async function myf () { let gf = await name("xiaohua"); let gf2 = await name("xiaohong"); return gf + gf2 } async function myf3 (params) { let aaa = await myf(); return aaa; } myf3().then(function (params) { console.log(params); }); // xiaohuaxiaohong
async/await 對前者的generater/yield 進行了高度的封裝配合那些支持 promise 實現(xiàn)的庫可以完美的像普通函數(shù)一樣調(diào)用, 并且async函數(shù)與其他async函數(shù)也可以完美無縫連接, 堪稱終極方案koa2已經(jīng)支持 async/await 但是最新的 express框架依然沒有支持這種寫法, async/await 是大勢所趨, 或許不久的將來 express也會支持它, 我們拭目以待
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107112.html
摘要:如果一個即時定時器是被一個正在執(zhí)行的回調(diào)排入隊列的,則該定時器直到下一次事件循環(huán)迭代才會被觸發(fā)。參數(shù)描述在事件循環(huán)的當前回合結束時要調(diào)用的函數(shù)。事件輪詢隨后的調(diào)用,會在任何事件包括定時器之前運行。 系列文章 Nodejs高性能原理(上) --- 異步非阻塞事件驅動模型Nodejs高性能原理(下) --- 事件循環(huán)詳解 前言 終于開始我nodejs的博客生涯了,先從基本的原理講起.以前寫...
摘要:最近在學習,它是使用的響應式編程的庫,它使編寫異步或基于回調(diào)的代碼更容易。返回的是一個對于指定的。發(fā)送請求使用了,雖然了多次,但是僅發(fā)送一次請求,了結果。中有一些操作符可以讓監(jiān)聽強制為異步的方式,例如。最近在學習RxJS,它是使用 Observables 的響應式編程的庫,它使編寫異步或基于回調(diào)的代碼更容易。 下面主要介紹Observables 與 promise的不同點。 單值與多值 c...
摘要:對于,除非使用箭頭函數(shù),它的回調(diào)函數(shù)的將會變化。使用測試下面的代碼,結果如下打印打印要點使用的規(guī)則要求所有回調(diào)函數(shù)必須使用箭頭函數(shù)。 譯者按: JS 騷操作。 原文:For vs forEach() vs for/in vs for/of in JavaScript 譯者: Fundebug 本文采用意譯,版權歸原作者所有 我們有多種方法來遍歷 JavaScript 的數(shù)組或者...
摘要:使用它可以讓頁面請求少量的數(shù)據(jù),而不用刷新整個頁面。這是一個比較粗糙的,不符合關注分離的設計原則,配置和使用都不是那么友好。它的一個優(yōu)勢異步操作,但的異步操作是基于事件的異步模型,沒有那么友好。 Ajax 是什么? 答:Ajax是一種可以在瀏覽器和服務器之間使用異步數(shù)據(jù)傳輸(HTTP請求)的技術。使用它可以讓頁面請求少量的數(shù)據(jù),而不用刷新整個頁面。而傳統(tǒng)的頁面(不使用Ajax)要刷新...
摘要:引言作為服務器的優(yōu)勢就在于適合處理高并發(fā)的請求,對于網(wǎng)站后臺這種密集型的后臺尤其有優(yōu)勢,其核心就在于是一個異步非阻塞模型。關于異步,同步,阻塞,非阻塞這些概念,本文不做討論。另外兩個的調(diào)用時間需要判斷是否都在主線程中被執(zhí)行。 引言 node作為服務器的優(yōu)勢就在于適合處理高并發(fā)的請求,對于web網(wǎng)站后臺這種I/O密集型的后臺尤其有優(yōu)勢,其核心就在于node是一個異步非阻塞模型。關于異步,...
閱讀 3900·2021-11-18 13:19
閱讀 1179·2021-10-11 10:58
閱讀 3286·2019-08-29 16:39
閱讀 3139·2019-08-26 12:08
閱讀 2034·2019-08-26 11:33
閱讀 2459·2019-08-23 18:30
閱讀 1306·2019-08-23 18:21
閱讀 2520·2019-08-23 18:18