Download - Node.js flow control
Node.js Flow ControlMiCloud - Simon
首先,今天
都是程式碼....
● node.js● text editor● command line tool● coffee… (可以清醒一點>.<)
請先準備好你的環境...
● modules?● callback?● closure?● json?
測試一下你對Node.js的認識
Modules
[test.js]
var say = require(./say’);say.hello(‘simon’)
[say.js]
exports.hello = function(word) { console.log( ‘Hello ’ + word);}
Callback
[say.js]
exports.hello = function(word, fn) { fn(‘Hello ’ + word);}
[test.js]var say = require(./say’);say.hello(‘simon’, function(word){
console.log(word);})
Closure
[say.js]
var f = function(x,y){ return x + y; }
function foo(callback) { var a = 1; var b = 2; callback(a,b); }
foo(f);
JSON
[test.js]
var a = {name:’simon’};a.sex = ‘man’;a.say = function(word) { console.log(‘Hello’ + word);}
console.log(a.name);a.say(a.name);delete a.sex;
大綱
● Write a function with callback● 基本的流程控制作法
○ array queue流程控制○ process.nextTick() + callee
● 流程控制套件介紹○ step○ q○ node-promise
● Web流程控制○ expressjs
Write a function with callbackvar request = require('request') , util = require('util');var url_ds = 'http://odf.micloud.tw/odf/datasets';var url_field = 'http://odf.micloud.tw/odf/%s/field';request.get(url_ds, function(err, res, body){ if(err) console.log(err); var data = JSON.parse(body); for(var i = 0 ; i < data.length ; i++){ var ds = data[i]; var url = util.format(url_field, ds); request.get(url, function(e,r,b){ if(e) console.log(e); console.log('Dataset: %s', ds); console.log(b); }); }});
沒有做好流程控制,你會有更多Callback...
基本的流程控制作法
插播一下:for 與 .forEach的差別
var arr = [0,1,2,3,4,5,6,7,8,9];
arr.forEach(function(v){ if(v == 3) return; console.log(v);})
var arr = [0,1,2,3,4,5,6,7,8,9];
for(var i = 0; i< arr.length ; i++){ var v = arr[i]; if(v == 3) return; console.log(v);}
結果是:0,1,2 結果是:0,1,2,4,5,6,7,8,9
var request = require('request');var queue = [ 'http://www.google.com', 'http://micloud.tw/ch/', 'http://tw.yahoo.com'];queue.forEach(function(v){ var t0 = new Date().getTime(); request({ url: v, method: 'GET' }, function(e,r,d) { var t1 = new Date().getTime(); if(e) console.log(e); console.log('[%s][%s]%s', v, t1-t0, d.substring(0,50)); });});
Case - 網路爬蟲範例
reqeust會在瞬間併發,有可能會被server當做是攻擊...
var request = require('request');var queue = ['http://www.google.com','http://micloud.tw','http://tw.yahoo.com'];function main(){ var t0 = new Date().getTime(); var _this = queue.pop(); request({url: _this,method: 'GET'}, function(e, r, d){ if(e) console.log(e); var t1 = new Date().getTime(); console.log('[%s][%s]%s', _this, t1-t0, d.substring(0,50)); if(queue.length > 0) { main(); } });}main();
解法(1):自己Call自己的Loop
如果queue還有值,才會call下一個request...
插播一下:callee/caller
解法(2):process.nextTick + callee
var request = require('request');var queue = ['http://www.google.com','http://micloud.tw','http://tw.yahoo.com'];
process.nextTick(function fn1(){ var url = queue.pop(); console.log('Processing %s...', url); var _callee = arguments.callee; request.get(url, function(e,r,d){ if(e) console.log(e); console.log('[%s] word count: %s', url, d.length); if(queue.length > 0) process.nextTick(_callee); });});
如果queue還有值,才會call下一個request...
因為Scope的關係,callee需要先指定給另一個變數,後面才能取用 ...
3’rd party modulesStep, q, node-promise
Step的用法
var Step = require('step');
Step( function step1() { console.log('Step1...'); throw 'error..'; //這個會掉到step2的arguments[0] return 123; //有return才會往下走
}, function step2() { console.log('Step2...'); console.log(arguments); //可以觀察接到的參數
return 223; }, function step3() { console.log('Step3...'); console.log(arguments); });
var request = require('request');var queue = ['http://www.google.com', 'http://micloud.tw','http://tw.yahoo.com'];var Step = require('step');Step( function step1() { console.log('Step1...'); getUrl(queue[0], this); }, function step2() { console.log('Step2...'); getUrl(queue[1], this); }, function step3() { console.log('Step3...'); getUrl(queue[2], this); });
使用Step操作爬蟲
function getUrl(url, callback) { //console.log('Processing url:%s...', url); request.get(url, function(e,r,d){ if(e) console.log(e); console.log('[%s] word count:%s', url, d.length); callback(e,d.length); })}
其他功能 - parallel()
Step( // Loads two files in parallel function loadStuff() { fs.readFile(__filename, this.parallel()); fs.readFile("/etc/passwd", this.parallel()); }, // Show the result when done function showStuff(err, code, users) { if (err) throw err; console.log(code); console.log(users); })
其他功能 - group()
Step( function readDir() { fs.readdir(__dirname, this); }, function readFiles(err, results) { if (err) throw err; var group = this.group(); // Create a new group results.forEach(function (filename) { fs.readFile(__dirname + "/" + filename, 'utf8', group()); }); }, function showAll(err , files) { if (err) throw err; console.dir(files); });
Step注意事項
● Step的操作是擷取callback作為下一個function的input
● 如果沒有callback的step,必須要return● 中間step(ex: step2)若沒有return,則程式有可
能會卡住不動(ex: web app)● 每個step function中的return不代表中斷整個
流程
CommonJS - Promises/A
■ Q Works in both NodeJS and browsers, compatible with jQuery, thenable, and usable as remote objects via message passing
■ RSVP.js A lightweight library that provides tools for organizing asynchronous code
■ when.js Compact promises with joining and chaining
■ node-promise Promises for NodeJS■ jQuery 1.5 is said to be 'based on the
CommonJS Promises/A design'.■ ForbesLindesay/promises-a A bare bones
implementation of Promises/A intended to pass https://github.com/domenic/promise-tests while being as small as possible
■ WinJS / Windows 8 / Metro
q模組
● Github: https://github.com/kriskowal/q● 範例: https://github.com/kriskowal/q/wiki/Examples-Gallery
安裝q模組
npm install q --save
q提供的功能
● Q.delay● Q.defer● Q.nfcall● Q.nfapply● Q.ninvoke● Q.npost● ...
var Q = require('q') , request = require('request') , queue = ['http://www.google.com','http://micloud.tw','http://tw.yahoo.com'];var fn = function(url) { var deferred = Q.defer(); request.get(url, function(e,r,d){ console.log('[%s] word count:%s', url, d.length); deferred.resolve(); }); return deferred.promise;};Q.allResolved( [ fn(queue[0]), fn(queue[1]), fn(queue[2]) ] ) .then(function(){ console.log(‘end...’) }).done();
使用q操做爬蟲
q只保證開始順序
延續剛剛的範例
…(skip)var out = function (x, y, z) { var d = Q.defer(); console.log('x:%s, y:%s, z:%s', x, y, z); d.resolve(); return d.promise;};
Q.allResolved([fn(queue[0]), fn(queue[1]), fn(queue[2])]) .spread(out) .then(function(){ console.log('end...'); }) .done();
使用spread來接收執
行結果值
node-promise模組
● Github: https://github.com/kriszyp/node-promise
安裝node-promise模組
npm install node-promise --save
var Promise = require("node-promise").Promise , request = require('request');var p = new Promise();function step1(){ request.get("http://www.google.com", function(e,r,d){ console.log('>>1'); p.resolve(d); });}step1();p.then(function(d){ console.log('>>2'); console.log('word count:%s', d.length); }, function(err){ console.log(err); })
使用node-promise操做爬蟲
可以透過then來取回
resolve的回傳值
var Promise = require("node-promise").Promise , request = require('request')var queue = ["http://www.google.com", "http://micloud.tw", "http://tw.yahoo.com"];var p;function step1(url){ p = new Promise(); request({ url : url, method : "GET" }, function(e,r,d){ console.log('>>url:%s', url); p.resolve(d); });}step1(queue[0]);step1(queue[1]);
錯誤的操作範例
因為promise(p)的scope問
題,會導致runtime
exception
var Promise = require("node-promise").Promise , request = require('request') , util = require('util')var site = ‘http://odf.micloud.tw’var url = site + '/odf/datasets'var url_detail = site + '/odf/%s/field';function step1(url){ var p = new Promise(); request({ url : url, method : "GET" }, function(e,r,d){ p.resolve(JSON.parse(d)); }); return p;}
延續剛剛的範例
step1(url).then(function(d){ for(var i = 0 ; i< d.length ; i++){ step1(util.format(url_detail, d[i])) .then(function(d){ console.log(d); }); }})
使用return promise的方式,串連then操作
使用function scope限制promise存續區域
Web Flow Controlexpressjs
ExpressJS中的流程控制
● next()
透過流程控制增加authfilter
//增加req.headers.auth認證
function authfilter(req, res, next){ if(req.headers.auth == 'demo') next(); //next代表通過此filter else //認證錯誤,則回傳statusCode與錯誤訊息
res.send(401, 'Auth Error!'); }
//則在routing中可以安插在真正執行route之前
app.get('/users', authfilter, user.list);
Reference
● http://opennodes.arecord.us● http://calculist.org/blog/2011/12/14/why-
coroutines-wont-work-on-the-web/● http://wiki.commonjs.org/wiki/Promises/A● https://github.com/basicallydan/q-
examples/blob/master/wait-for-multiple-promises.js
● https://github.com/kriskowal/q/wiki/API-Reference
End...