標籤:blog http io ar 使用 java sp div on
在js或者node編程中,由於非同步頻繁和廣度使用,使得回調和嵌套的深度導致編程的體驗遇到一些挑戰,如果寫出優雅和好看的代碼,本文主要針對非同步編程的主流方案做一些總結
1、事件發布/訂閱模式
事件監聽器模式是一種廣泛用於非同步編程的模式, 是回呼函數的事件化,又稱發布/訂閱模式, node自身提供events模組,是該模式的一個簡單實現。
EventPorxy
2、promise/deferrd模式
在2009年被Kris Zyp抽象為一個提議草案,發布在CommonJS規範中, 目前,CommonJS草案中已經包括Promise/A、Promise/B、Promise/D這些非同步模型。由於Promise/A較為常用也較為簡單,只需要具備then()方法即可。所以先介紹一些改模式。
一般來說,then()的方法定義如下:
then(fulfilledHandler, errorHandler, progressHandler)
Promises/A
通過繼承Node的events模組,我們可以實現一個簡單Promise模組。
promise部分
var Promise = function() { EventEmitter.call(this);}util.inherits(Promise, EventEmitter); // util是node內建的工具類 Promise.prototype.then = function(fulfilledHandler, errorHandler, progressHandler) { if(typeof fulfilledHandler === "function") { this.once(‘success‘, fulfilledHandler); } if(typeof errorHandler === "function") { this.once(‘error‘, errorHandler); } if(typeof progressHandler === "function") { this.on(‘progress‘, progressHandler); } return this;}
deferred部分
var Deferred = function() { this.state = ‘unfulfilled‘; this.promise = new Promise();} Deferred.prototype.resolve = function(obj) { this.state = ‘fulfilled‘; this.promise.emit(‘success‘, obj);} Deferred.prototype.reject = function(obj) { this.state = ‘failed‘; this.promise.emit(‘error‘, obj);} Deferred.prototype.progress = function(obj) { this.promise.emit(‘progress‘, obj);}
使用
function readFile(file, encoding) { var deferred = new Deferred(); fs.readFile(file, encoding, deferred.resolve); return deferred.promise;}readFile(‘/test.txt‘, ‘utf-8‘).then(function(data) { ... }, function(err) { ...});
以上是promise/deferred模式的簡單實現,狀態轉換圖可以描述如下:
promise模式比發布/訂閱模式略為優雅, 但還不能滿足很多情境的實際需求,比如一組純非同步API為了協同完成一串事情。
Promises/A+
規範將之前 Promises/A 規範的建議明確為了行為標準。其擴充了原規範以覆蓋一些約定俗成的行為,以及省略掉一些僅在特定情況下存在的或者有問題的部分
詳見:中文版:http://www.ituring.com.cn/article/66566, 英文版:https://promisesaplus.com/
3、流程式控制制庫尾觸發與next
尾觸發目前應用最多的地方是Connect的中介軟體, 中介軟體處理網路請求時,可以向面向切面編程一樣進行過濾、驗證、日誌等功能,最簡單的中介軟體如下:
function(req, res, next) { //中介軟體 }
每個中介軟體傳遞請求對象、響應對象和尾觸發函數,通過隊列形成一個處理流,如下:
看一個例子
app.use(function(req, res, next) { setTimeout(function() { next(); }, 0) }, function(req, res, next) { setTimeout(function() { next(); }, 0);});
從這個執行個體中可以簡單猜到尾觸發的實現原理了,簡單講就是通過調用use維護一個隊列, 調用next的時候出隊並執行,依次迴圈。
async
目前最知名的流程式控制制模組,async模組提供了20多個方法用於處理非同步多種寫作模式, 如:
1、非同步串列執行
async.series([ function(callback){ // do some stuff ... callback(null, ‘one‘); }, function(callback){ // do some more stuff ... callback(null, ‘two‘); }],// optional callbackfunction(err, results){ // results is now equal to [‘one‘, ‘two‘]});
// an example using an object instead of an arrayasync.series({ one: function(callback){ setTimeout(function(){ callback(null, 1); }, 200); }, two: function(callback){ setTimeout(function(){ callback(null, 2); }, 100); }},function(err, results) { // results is now equal to: {one: 1, two: 2}});
異常處理原則是一遇到異常,即結束所有調用,並將異常傳遞給最終回呼函數的第一個參數
2、非同步並存執行
// an example using an object instead of an arrayasync.parallel({ one: function(callback){ setTimeout(function(){ callback(null, 1); }, 200); }, two: function(callback){ setTimeout(function(){ callback(null, 2); }, 100); }},function(err, results) { // results is now equals to: {one: 1, two: 2}});
與EventProxy基於事件發布和訂閱模式的不同在於回呼函數的使用上, async回呼函數由async封裝後傳入, 而EventProxy則通過done(), fail()方法來產生新的回呼函數, 實現方式都是高階函數的應用。
3、非同步呼叫的依賴處理
async.waterfall([ function(callback){ callback(null, ‘one‘, ‘two‘); }, function(arg1, arg2, callback){ // arg1 now equals ‘one‘ and arg2 now equals ‘two‘ callback(null, ‘three‘); }, function(arg1, callback){ // arg1 now equals ‘three‘ callback(null, ‘done‘); }], function (err, result) { // result now equals ‘done‘ });
4、自動依賴處理
async.auto({ get_data: function(callback){ console.log(‘in get_data‘); // async code to get some data callback(null, ‘data‘, ‘converted to array‘); }, make_folder: function(callback){ console.log(‘in make_folder‘); // async code to create a directory to store a file in // this is run at the same time as getting the data callback(null, ‘folder‘); }, write_file: [‘get_data‘, ‘make_folder‘, function(callback, results){ console.log(‘in write_file‘, JSON.stringify(results)); // once there is some data and the directory exists, // write the data to a file in the directory callback(null, ‘filename‘); }], email_link: [‘write_file‘, function(callback, results){ console.log(‘in email_link‘, JSON.stringify(results)); // once the file is written let‘s email a link to it... // results.write_file contains the filename returned by write_file. callback(null, {‘file‘:results.write_file, ‘email‘:‘[email protected]‘}); }]}, function(err, results) { console.log(‘err = ‘, err); console.log(‘results = ‘, results);});
在現實的業務環境中,具有很多複雜的依賴關係, 並且同步和非同步也不確定,為此auto方法能根據依賴關係自動分析執行。
Step
輕量的async, 在API暴露上也具備一致性, 因為只有一個介面Step。
在非同步處理上有一些不同, Step一旦產生異常,會將異做為下一個方法的第一個參數傳入
var s = require(‘step‘);s( function readSelf() { fs.readFile(__filename, this); }, function(err, content) { //並存執行任務 fs.readFile(__filename, this.parallel()); fs.readFile(__filename, this.parallel()); }, function() {
//任務分組儲存結果 var group = this.group(); console.log(arguments); fs.readFile(__filename, group()); fs.readFile(__filename, group()); }, function () { console.log(arguments); } )Wind
待補充
總結
對比幾種方案的區別:事件發布/訂閱模式相對是一種原始的方式,Promise/Deferred模式貢獻了一個非常不錯的非同步任務模型的抽象,重頭在於封裝非同步調用部分, 而流程式控制制庫則要靈活很多。
除了async、step、EventProxy、wind等方案外,還有一類通過原始碼編譯的方案來實現流程式控制制的簡化, streamline是一個典型的例子。
參考
《深入淺出nodejs》第四章
https://promisesaplus.com/
https://github.com/caolan/async/
https://github.com/creationix/step
http://www.ituring.com.cn/article/66566
nodejs學習筆記 —— 非同步編程解決方案