摘要:而之后,我們得到的是一個是一個對象,我們可以使用語句定義回調(diào)函數(shù),函數(shù)的內(nèi)容呢,則是將讀取到的返回給并繼續(xù)讓從斷點處執(zhí)行。
在上一篇中我們梳理了koa當中中間件的洋蔥模型執(zhí)行原理,并實現(xiàn)了一個可以讓洋蔥模型自動跑起來的流程管理函數(shù)。這一篇,我們再來研究一下koa當中異步回調(diào)同步化寫法的原理,同樣的,我們也會實現(xiàn)一個管理函數(shù),是的我們能夠通過同步化的寫法來寫異步回調(diào)函數(shù)。
1. 回調(diào)金字塔及理想中的解決方案我們都知道javascript是一門單線程異步非阻塞語言。異步非阻塞當然是它的一個優(yōu)點,但大量的異步操作必然涉及大量的回調(diào)函數(shù),特別是當異步嵌套的時候,就會出現(xiàn)回調(diào)金字塔的問題,使得代碼的可讀性非常差。比如下面一個例子:
var fs = require("fs"); fs.readFile("./file1", function(err, data) { console.log(data.toString()); fs.readFile("./file2", function(err, data) { console.log(data.toString()); }) })
這個例子是先后讀取兩個文件內(nèi)容并打印,其中file2的讀取必須在file1讀取結束之后再進行,因此其操作必須要在file1讀取的回調(diào)函數(shù)中執(zhí)行。這是一個典型的回調(diào)嵌套,并且只有兩層而已,在實際編程中,我們可能會遇到更多層的嵌套,這樣的代碼寫法無疑是不夠優(yōu)雅的。
在我們想象中,比較優(yōu)雅的一種寫法應該是看似同步實則異步的寫法,類似下面這樣:
var data; data = readFile("./file1"); //下面的代碼是第一個readFile執(zhí)行完畢之后的回調(diào)部分 console.log(data.toString()); //下面的代碼是第二個readFile的回調(diào) data = readFile("./file2"); console.log(data.toString());
這樣的寫法,就完全避免回調(diào)地獄。事實上,koa就讓我們可以使用這樣的寫法來寫異步回調(diào)函數(shù):
var koa = require("koa"); var app = koa(); var request=require("some module"); app.use(function*() { var data = yield request("http://www.baidu.com"); //以下是異步回調(diào)部分 this.body = data.toString(); }) app.listen(3000);
那么,究竟是什么讓koa有這么神奇的魔力呢?
2. generator配合promise實現(xiàn)異步回調(diào)同步寫法關鍵的一點,其實前一篇也提到了,就是generator具有類似"打斷點"這樣的效果。當遇到y(tǒng)ield的時候,就會暫停,將控制權交給yield后面的函數(shù),當下次返回的時候,再繼續(xù)執(zhí)行。
而在上面的那個koa例子中,yield后面的可不是任何對象都可以哦!必須是特定類型。在co函數(shù)中,可以支持promise, thunk函數(shù)等。
今天的文章中,我們就以promise為例來進行分析,看看如何使用generator和promise配合,實現(xiàn)異步同步化。
依舊以第一個讀取文件例子來分析。首先,我們需要將讀文件的函數(shù)進行改造,將其封裝成為一個promise對象:
var fs = require("fs"); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //下面是readFile使用的示例 var tmp = readFile("./file1"); tmp.then(function(data) { console.log(data.toString()); })
關于promise的使用,如果不熟悉的可以去看看es6中的語法。(近期我也會寫一篇文章來教大家如何用es5的語法來自己實現(xiàn)一個具備基本功能的promise對象,敬請期待呦^_^)
簡單來講,promise可以實現(xiàn)將回調(diào)函數(shù)通過 promise.then(callback)的形式來寫。但是我們的目標是配合generator,真正實現(xiàn)如絲般順滑的同步化寫法,如何配合呢,看這段代碼:
var fs = require("fs"); var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //將讀文件的過程放在generator中 var gen = function*() { var data = yield readFile("./file1"); console.log(data.toString()); data = yield readFile("./file2"); console.log(data.toString()); } //手動執(zhí)行generator var g = gen(); var another = g.next(); //another.value就是返回的promise對象 another.value.then(function(data) { //再次調(diào)用g.next從斷點處執(zhí)行generator,并將data作為參數(shù)傳回 var another2 = g.next(data); another2.value.then(function(data) { g.next(data); }) })
上述代碼中,我們在generator中yield了readFile,回調(diào)語句代碼寫在yield之后的代碼中,完全是同步的寫法,實現(xiàn)了文章一開頭的設想。
而yield之后,我們得到的是一個another.value是一個promise對象,我們可以使用then語句定義回調(diào)函數(shù),函數(shù)的內(nèi)容呢,則是將讀取到的data返回給generator并繼續(xù)讓generator從斷點處執(zhí)行。
基本上這就是異步回調(diào)同步化最核心的原理,事實上如果大家熟悉python,會知道python中有"協(xié)程"的概念,基本上也是使用generator來實現(xiàn)的(我想當懷疑es6的generator就是借鑒了python~)
不過呢,上述代碼我們依然是手動執(zhí)行的。那么同上一篇一樣,我們還需要實現(xiàn)一個run函數(shù),用于管理generator的流程,讓它能夠自動跑起來!
3. 讓同步化回調(diào)函數(shù)自動跑起來:一個run函數(shù)的編寫仔細觀察上一段代碼中手動執(zhí)行generator的部分,也能發(fā)現(xiàn)一個規(guī)律,這個規(guī)律讓我們可以直接寫一個遞歸的函數(shù)來代替:
var run=function(gen){ var g; if(typeof gen.next==="function"){ g=gen; }else{ g=gen(); } function next(data){ var tmp=g.next(data); if(tmp.done){ return ; }else{ tmp.value.then(next); } } next(); }
函數(shù)接收一個generator,并讓其中的異步能夠自動執(zhí)行。使用這個run函數(shù),我們來讓上一個異步代碼自動執(zhí)行:
var fs = require("fs"); var run = function(gen) { var g; if (typeof gen.next === "function") { g = gen; } else { g = gen(); } function next(data) { var tmp = g.next(data); if (tmp.done) { return; } else { tmp.value.then(next); } } next(); } var readFile = function(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function(err, data) { if (err) { reject(err); } else { resolve(data); } }) }) } //將讀文件的過程放在generator中 var gen = function*() { var data = yield readFile("./file1"); console.log(data.toString()); data = yield readFile("./file2"); console.log(data.toString()); } //下面只需要將gen放入run當中即可自動執(zhí)行 run(gen);
執(zhí)行上述代碼,即可看到終端依次打印出了file1和file2的內(nèi)容。
需要指出的是,這里的run函數(shù)為了簡單起見只支持promise,而實際的co函數(shù)還支持thunk等。
這樣一來,co函數(shù)的兩大功能基本就完整介紹了,一個是洋蔥模型的流程控制,另一個是異步同步化代碼的自動執(zhí)行。在下一篇文章中,我將帶大家對這兩個功能進行整合,寫出我們自己的一個co函數(shù)!
這篇文章的代碼同樣可以在github上面找到:https://github.com/mly-zju/async-js-demo,其中promise_generator.js就是本篇的示例源碼。
同樣歡迎大家多多關注我的github pages個人博客哦,會不定期更新我的技術文章~
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/80967.html
摘要:到此為止,我們就基本講清楚了中的中間件洋蔥模型是如何自動執(zhí)行的。 koa被認為是第二代web后端開發(fā)框架,相比于前代express而言,其最大的特色無疑就是解決了回調(diào)金字塔的問題,讓異步的寫法更加的簡潔。在使用koa的過程中,其實一直比較好奇koa內(nèi)部的實現(xiàn)機理。最近終于有空,比較深入的研究了一下koa一些原理,在這里會寫一系列文章來記錄一下我的學習心得和理解。 在我看來,koa最核心...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:感謝大神的免費的計算機編程類中文書籍收錄并推薦地址,以后在倉庫里更新地址,聲音版全文狼叔如何正確的學習簡介現(xiàn)在,越來越多的科技公司和開發(fā)者開始使用開發(fā)各種應用。 說明 2017-12-14 我發(fā)了一篇文章《沒用過Node.js,就別瞎逼逼》是因為有人在知乎上黑Node.js。那篇文章的反響還是相當不錯的,甚至連著名的hax賀老都很認同,下班時讀那篇文章,竟然坐車的還坐過站了。大家可以很...
摘要:許多公司都投資于或之類的靜態(tài)分析安全測試,解決方案。用靜態(tài)分析方法確保編程安全一書詳細描述了靜態(tài)分析技術的基本原理。博士將靜態(tài)分析無法找出的諸多安全問題歸為瑕疵,而非程序錯誤。 靜態(tài)分析安全測試(SAST)是指不運行被測程序本身,僅通過分析或者檢查源程序的語法、結構、過程、接口等來檢查程序的正確性,那么采用靜分析安全測試的方法有什么優(yōu)缺點呢,且讓小編給你說道說道。 許多公司都投資于 H...
閱讀 1811·2021-10-09 09:44
閱讀 3382·2021-09-28 09:35
閱讀 1372·2021-09-01 10:31
閱讀 1658·2019-08-30 15:55
閱讀 2697·2019-08-30 15:54
閱讀 923·2019-08-29 17:07
閱讀 1372·2019-08-29 15:04
閱讀 2001·2019-08-26 13:56