日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
淺析Node.js中的流程控制

對(duì)于在node這種異步框架下的編程,唯一的難題是:如何控制哪些函數(shù)順序執(zhí)行,哪些函數(shù)并行執(zhí)行。node中并沒有內(nèi)置的控制方法,在這里我分享編寫本站程序時(shí)用到的一些技巧。

并行VS順序

在應(yīng)用程序中通常有一些步驟必須在先前的操作得出結(jié)果之后才能運(yùn)行。在平常的順序執(zhí)行的程序中這非常容易解決,因?yàn)槊恳徊糠侄急仨毜却耙徊糠謭?zhí)行完畢才能執(zhí)行。

Node中,除了那些執(zhí)行阻塞IO的方法,其他方法都會(huì)存在這個(gè)問題。比如,掃描文件夾、打開文件、讀取文件內(nèi)容、查詢數(shù)據(jù)庫等等。

對(duì)于我的博客引擎,有一些以樹形結(jié)構(gòu)組織的文件需要處理。步驟如下所示:

◆ 獲取文章列表 (譯者注:作者的博客采取文件系統(tǒng)存儲(chǔ)文章,獲取文章列表,起始就是掃描文件夾)。

◆ 讀入并解析文章數(shù)據(jù)。

◆ 獲取作者列表。

◆ 讀取并解析作者數(shù)據(jù)。

◆ 獲取HAML模版列表。

◆ 讀取所有HAML模版。

◆ 獲取資源文件列表。

◆ 讀取所有資源文件。

◆ 生成文章html頁面。

◆ 生成作者頁面。

◆ 生成索引頁(index page)。

◆ 生成feed頁。

◆ 生成靜態(tài)資源文件。

如你所見,有些步驟可以不依賴其他步驟獨(dú)立執(zhí)行(但有些不行)。例如,我可以同時(shí)讀取所有文件,但必須在掃描文件夾獲取文件列表之后。我可以同時(shí)寫入所有文件,但是必須等待文件內(nèi)容都計(jì)算完畢才能寫入。

使用分組計(jì)數(shù)器

對(duì)于如下這個(gè)掃貓文件夾并讀取其中文件的例子,我們可以使用一個(gè)簡(jiǎn)單的計(jì)數(shù)器:

 
 
 
  1. var fs = require('fs');  
  2.  
  3. fs.readdir(".", function (err, files) {  
  4.   var count = files.length,  
  5.       results = {};  
  6.   files.forEach(function (filename) {  
  7.     fs.readFile(filename, function (data) {  
  8.       results[filename] = data;  
  9.       count--;  
  10.       if (count <= 0) {  
  11.         // Do something once we know all the files are read.  
  12.       }  
  13.     });  
  14.   });  
  15. });  

嵌套回調(diào)函數(shù)是保證它們順序執(zhí)行的好方法。所以在readdir回調(diào)函數(shù)中,我們根據(jù)文件數(shù)量設(shè)定了一個(gè)倒數(shù)計(jì)數(shù)器。然后我們對(duì)每個(gè)文件執(zhí)行readfile操作,它們將并行執(zhí)行并以任意順序完成。最重要的是,在每個(gè)文件讀取完成時(shí)計(jì)數(shù)器的值會(huì)減小1,當(dāng)它的值變?yōu)?的時(shí)候我們就知道文件全部讀取完畢了。

通過傳遞回調(diào)函數(shù)避免過度嵌套

在取得文件內(nèi)容之后,我們可以在最里層的函數(shù)中執(zhí)行其他操作。但是當(dāng)順序操作超過7級(jí)之后,這將很快成為一個(gè)問題。

讓我們使用傳遞回調(diào)的方式修改一下上面的實(shí)例:

 
 
 
  1. var fs = require('fs');  
  2.  
  3. function read_directory(path, next) {  
  4.   fs.readdir(".", function (err, files) {  
  5.     var count = files.length,  
  6.         results = {};  
  7.     files.forEach(function (filename) {  
  8.       fs.readFile(filename, function (data) {  
  9.         results[filename] = data;  
  10.         count--;  
  11.         if (count <= 0) {  
  12.           next(results);  
  13.         }  
  14.       });  
  15.     });  
  16.   });  
  17. }  
  18.  
  19. function read_directories(paths, next) {  
  20.   var count = paths.length,  
  21.       data = {};  
  22.   paths.forEach(function (path) {  
  23.     read_directory(path, function (results) {  
  24.       data[path] = results;  
  25.       count--;  
  26.       if (count <= 0) {  
  27.         next(data);  
  28.       }  
  29.     });  
  30.   });  
  31. }  
  32.  
  33. read_directories(['articles', 'authors', 'skin'], function (data) {  
  34.   // Do something  
  35. });  

現(xiàn)在我們寫了一個(gè)混合的異步函數(shù),它接收一些參數(shù)(本例中為路徑),和一個(gè)在完成所有操作后調(diào)用的回調(diào)函數(shù)。所有的操作都將在回調(diào)函數(shù)中完成,最重要的是我們將多層嵌套轉(zhuǎn)化為一個(gè)非嵌套的回調(diào)函數(shù)。

Combo庫

我利用空閑時(shí)間編寫了一個(gè)簡(jiǎn)單的Combo庫?;旧?,它封裝了進(jìn)行事件計(jì)數(shù),并在所有事件完成之后調(diào)用回調(diào)函數(shù)的這個(gè)過程。同時(shí)它也保證不管回調(diào)函數(shù)的實(shí)際執(zhí)行時(shí)間,都能保證它們按照注冊(cè)的順序執(zhí)行。

 
 
 
  1. function Combo(callback) {  
  2.   this.callback = callback;  
  3.   this.items = 0;  
  4.   this.results = [];  
  5. }  
  6.  
  7. Combo.prototype = {  
  8.   add: function () {  
  9.     var self = this,  
  10.         id = this.items;  
  11.     this.items++;  
  12.     return function () {  
  13.       self.check(id, arguments);  
  14.     };  
  15.   },  
  16.   check: function (id, arguments) {  
  17.     this.results[id] = Array.prototype.slice.call(arguments);  
  18.     this.items--;  
  19.     if (this.items == 0) {  
  20.       this.callback.apply(this, this.results);  
  21.     }  
  22.   }  
  23. };  

如果你想從數(shù)據(jù)庫和文件中讀取數(shù)據(jù),并在完成之后執(zhí)行一些操作,你可以如下進(jìn)行:

 
 
 
  1. // Make a Combo object.  
  2. var both = new Combo(function (db_result, file_contents) {  
  3.   // Do something  
  4. });  
  5. // Fire off the database query  
  6. people.find({name: "Tim", age: 27}, both.add());  
  7. // Fire off the file read  
  8. fs.readFile('famous_quotes.txt', both.add());  

數(shù)據(jù)庫查詢和文件讀取將同時(shí)開始,當(dāng)他們?nèi)客瓿芍?,傳遞給combo構(gòu)造函數(shù)的回調(diào)函數(shù)將會(huì)被調(diào)用。第一個(gè)參數(shù)是數(shù)據(jù)庫查詢結(jié)果,第二個(gè)參數(shù)是文件讀取結(jié)果。

結(jié)論

本篇文章中介紹的技巧:

◆ 通過嵌套回調(diào),得到順序執(zhí)行的行為。

◆ 通過直接函數(shù)調(diào)用,得到并行執(zhí)行的行為。

◆ 通過回調(diào)函數(shù)來化解順序操作造成的嵌套。

◆ 使用計(jì)數(shù)器檢測(cè)一組并行的操作什么時(shí)候完成。

◆ 使用類似combo這樣的庫來簡(jiǎn)化操作。

#p#

上一篇介紹流程控制的文章給我?guī)砹撕艽蟮臉啡ぃF(xiàn)在我想要處理一些反饋,另外還要討論一下inimino所作的偉大工作。

當(dāng)前node中有兩種處理異步返回值的方法:promises和event emitters。關(guān)于兩種方法的細(xì)節(jié),你可以閱讀nodejs.org上的介紹。我將會(huì)討論這兩種方法和另一種處理異步返回值和流事件(streaming events)的方法。

為什么要區(qū)分Promise和EventEmitter?

在node中有兩種處理事件的類,它們是:Promise和EventEmitter。Promise是函數(shù)的異步表現(xiàn)形式。

 
 
 
  1. var File = require('file');  
  2. var promise = File.read('mydata.txt');  
  3. promise.addCallback(function (text) {  
  4.   // Do something  
  5. });  
  6. promise.addErrback(function (err) {  
  7.   // Handle error  
  8. })  

File.read接受文件名并返回文件內(nèi)容。

有時(shí)我們需要監(jiān)聽可能多次發(fā)生的事件。例如,在一個(gè)web服務(wù)中,處理web請(qǐng)求時(shí),body事件多次被觸發(fā),然后complete事件被觸發(fā)。

 
 
 
  1. Http.createServer(function (req, res) {  
  2.   var body = "";  
  3.   req.addListener('body', function (chunk) {  
  4.     body += chunk;  
  5.   });  
  6.   req.addListener('complete', function () {  
  7.     // Do something with body text  
  8.   });  
  9. }).listen(8080);  

這兩種方式的不同之處在于:在使用promise時(shí),你會(huì)得到success事件或者error事件,但不會(huì)同時(shí)得到,也不會(huì)得到一個(gè)以上事件。在處理會(huì)發(fā)生多次的事件的時(shí)候,你就需要更強(qiáng)大的 EventEmitters。

創(chuàng)建自定義promise

假定我想為posix.open, posix.write, 和posix.close寫一個(gè)便于使用的包裝函數(shù)filewrite。(如下代碼摘自”file”函數(shù)庫中File.write函數(shù)的真實(shí)代碼)

 
 
 
  1. function fileWrite (filename, data) {  
  2.   var promise = new events.Promise();  
  3.   posix.open(filename, "w", 0666)  
  4.     .addCallback(function (fd) {  
  5.       function doWrite (_data) {  
  6.         posix.write(fd, _data, 0, encoding)  
  7.           .addCallback(function (written) {  
  8.             if (written === _data.length) {  
  9.               posix.close(fd);  
  10.               promise.emitSuccess();  
  11.             } else {  
  12.               doWrite(_data.slice(written));  
  13.             }  
  14.           }).addErrback(function () {  
  15.             promise.emitError();  
  16.           });  
  17.       }  
  18.       doWrite(data);  
  19.     })  
  20.     .addErrback(function () {  
  21.       promise.emitError();  
  22.     });  
  23.   return promise;  
  24. };  

filewrite函數(shù)可以以如下形式使用:

 
 
 
  1. fileWrite("MyBlog.txt", "Hello World").addCallback(function () {  
  2.   // It's done  
  3. });  

請(qǐng)注意,我必須創(chuàng)建一個(gè)promise對(duì)象,執(zhí)行操作,然后將結(jié)果傳遞給這個(gè)promise對(duì)象。

還有更好的方法

promises工作良好,但是繼續(xù)讀過inimino之后,它所使用的方法令我印象深刻。

是否還記得我們的第一個(gè)例子?假設(shè)我們按照如下方式使用File.read:

 
 
 
  1. var File = require('file');  
  2. File.read('mydata.txt')(function (text) {  
  3.   // Do something  
  4. }, function (error) {  
  5.   // Handle error  
  6. });  

它不返回promise對(duì)象,而是返回一個(gè)接受兩個(gè)回調(diào)函數(shù)作為參數(shù)的函數(shù):一個(gè)處理成功,一個(gè)處理失敗。我把這種風(fēng)格成為“Do風(fēng)格”,下面我詳細(xì)解釋:

編寫回調(diào)風(fēng)格的代碼

如果我們想定義一個(gè)不立刻返回值的函數(shù)。使用”Do”風(fēng)格,filewirte函數(shù)應(yīng)當(dāng)如下使用:(假定之前提到的posix函數(shù)也是這個(gè)風(fēng)格)

 
 
 
  1. function fileWrite (filename, data) { return function (callback, errback) {  
  2.   posix.open(filename, "w", 0666)(function (fd) {  
  3.     function doWrite (_data) {  
  4.       posix.write(fd, _data, 0, encoding)(  
  5.         function (written) {  
  6.           if (written === _data.length) {  
  7.             posix.close(fd);  
  8.             callback();  
  9.           } else {  
  10.             doWrite(_data.slice(written));  
  11.           }  
  12.         }, errback);  
  13.     }  
  14.     doWrite(data);  
  15.   }, errback);  
  16. }};  

請(qǐng)注意,這樣很容易就把錯(cuò)誤信息返回給了調(diào)用者。同時(shí),這種風(fēng)格也使代碼更短,更易閱讀。

使用這種風(fēng)格編寫代碼的關(guān)鍵是:不要返回promise,而是返回一個(gè)接受兩個(gè)回調(diào)的函數(shù),在需要的時(shí)候直接調(diào)用返回的函數(shù)。

“Do”函數(shù)庫

前些日子我寫了一個(gè)小型的函數(shù)庫,叫做“Do”。實(shí)際上,它只有一個(gè)執(zhí)行并行操作的函數(shù),就像上一篇文章中介紹的“Combo”庫。

實(shí)現(xiàn)

如下是整個(gè)函數(shù)的實(shí)現(xiàn):

 
 
 
  1. Do = {  
  2.   parallel: function (fns) {  
  3.     var results = [],  
  4.         counter = fns.length;  
  5.     return function(callback, errback) {  
  6.       fns.forEach(function (fn, i) {  
  7.         fn(function (result) {  
  8.           results[i] = result;  
  9.           counter--;  
  10.           if (counter <= 0) {  
  11.             callback.apply(null, results);  
  12.           }  
  13.         }, errback);  
  14.       });  
  15.     }  
  16.   }  
  17. };  

結(jié)合回調(diào)風(fēng)格,使用這個(gè)函數(shù)可以寫出非常強(qiáng)大和簡(jiǎn)介的代碼。

執(zhí)行單個(gè)操作

我們假定有一個(gè)實(shí)現(xiàn)了這個(gè)新技巧的函數(shù)readFIle,可以如下使用這個(gè)函數(shù):

 
 
 
  1. // A single async action with error handling  
  2. readFile('secretplans.txt')(function (secrets) {  
  3.   // Do something  
  4. }, function (error) {  
  5.   // Handle Error  
  6. });  

執(zhí)行并行操作

我們繼續(xù)使用”Do"函數(shù)庫

 
 
 
  1. Do.parallel([  
  2.     readFile('mylib.js'),  
  3.     readFile('secretplans.txt'),  
  4. ])(function (source, secrets) {  
  5.   // Do something  
  6. }, function (error) {  
  7.   // Handle Error  
  8. });  

上述代碼代碼并行執(zhí)行了兩個(gè)異步操作,并在全部執(zhí)行完畢后執(zhí)行指定代碼。注意,如果沒有錯(cuò)誤發(fā)生,只有處理success的回調(diào)函數(shù)會(huì)被執(zhí)行。如果出錯(cuò),函數(shù)會(huì)將錯(cuò)誤傳遞給通常的錯(cuò)誤處理代碼。

你也可傳遞一個(gè)文件名數(shù)組。

 
 
 
  1. var files = ["one.txt", "two.txt", "three.txt"];  
  2. var actions = files.map(function (filename) {  
  3.   return readFile(filename);  
  4. });  
  5.  
  6. Do.parallel(actions)(function () {  
  7.   var contents = {},  
  8.       args = arguments;  
  9.   files.forEach(function (filename, index) {  
  10.     contents[filename] = args[index];  
  11.   });  
  12.   // Do something  
  13. });  
  14. // Let error thow exception.  

執(zhí)行順序操作

要執(zhí)行順序操作,只需將函數(shù)“串起來”即可:

 
 
 
  1. readFile('names.txt')(  
  2.   function upcase_slowly(string) { return function (next) {  
  3.     setTimeout(function () {  
  4.       next(string.toUpperCase());  
  5.     }, 100);  
  6.   }}  
  7. )(  
  8.   function save_data(string) { return function (next) {  
  9.     writeFile('names_up.txt', string)(next);  
  10.   }}  
  11. )(function () {  
  12.   // File was saved  
  13. });  

上述代碼讀取文件'names.txt',完成之后調(diào)用upcase_slowly,然后將生成的新字符串專遞給save_data函數(shù)。save_data函數(shù)是對(duì)writeFile的一個(gè)包裝。當(dāng)save_data函數(shù)執(zhí)行完畢之后,將執(zhí)行最終的回調(diào)函數(shù)。

Just for fun, here is the same example translated to the Jack language (still in development).

開個(gè)玩笑,如下代碼是翻譯成Jack語言(還在開發(fā)中)的示例代碼:

 
 
 
  1. readFile names.txt  
  2. | fun string -> next ->  
  3.   timeout 100, fun ->  
  4.     next string.toUpperCase()  
  5. | fun string -> next ->  
  6.   writeFile 'names_up.txt', string | next  
  7. | fun ->  
  8.  # File was saved  

原文:http://www.grati.org/?cat=35

【編輯推薦】

  1. 揭秘Node.js事件
  2. Node.js初探之hello world
  3. Node.js初探之與Mysql的交互
  4. Node.js入門之神秘的服務(wù)器端JavaScript
  5. 什么是Node.js?

當(dāng)前文章:淺析Node.js中的流程控制
網(wǎng)頁地址:http://m.5511xx.com/article/coejeoc.html