摘要:如果按照上面回調函數的寫法這種寫法看起來雖然簡單,但是如果在各個任務中都含有多個復雜的邏輯操作,需要串行操作的任務一旦變多,那么這種回調的寫法就可讀性非常差,而且難以維護。
接觸nodejs時間不長,如果有所紕漏,請大家批評指正
nodejs module q眾所周知,nodejs是異步的,但是何為異步呢?就是設置一個任務后立即返回,然后加上一個監聽,當任務結束的時候,就去調用監聽。
比如下面的代碼:
fs = require("fs") fs.readFile("/etc/hosts", "utf8", function (err,data) { // 設置了一個監聽,文件讀取完畢以后調用 if (err) { return console.log(err); } console.log(data); }); // 不阻塞,馬上返回 console.log("starting read file..."); // result: // starting read file... // host file content
要是對于簡單的任務,這是一個非常好的設計,給你一個任務,任務做好了馬上告訴我,然后觸發相應的操作
但是如果對于復雜的任務,就未必是一種好的做法了。比如我們現在有多個任務需要進行處理,但是這些任務之間存在以來關系,比如任務二需要在任務一完成以后才可以開始,任務三要在任務二后面才可以開始。。。如果按照上面回調函數的寫法:
task1(function (value1) { task2(value1, function(value2) { task3(value2, function(value3) { task4(value3, function(value4) { // Do something with value4 // ... more task ... // I am in the callback hell }); }); }); });
這種寫法看起來雖然簡單,但是如果在各個任務中都含有多個復雜的邏輯操作,需要串行操作的任務一旦變多,那么這種回調的寫法就可讀性非常差,而且難以維護。也就是所謂的回調地獄。
拿有沒有什么方法可以簡化這種復雜的操作呢?答案是肯定的,那就是nodejs里面的q模塊。
q模塊q模塊不僅僅是為了解決回調地獄的問題,它還能很大程度上輔助你進行一些需要并行,串行,定時等操作。
promisepromise是用來取代回調函數來進行異步操作的另一種方案
我們先來看一下大牛對promise的定義
A promise is an abstraction for asynchronous programming. It’s an object that proxies for the return value or the exception thrown by a function that has to do some asynchronous processing. — Kris Kowal on JSJ
我們做什么事都不要忘了最初的目的,我們最初采用回調的目的就是布置一件任務,任務結束以后,就將操作的數據傳入我們注冊的函數,我們再去處理數據。
promise做的事情也是同樣的目的,為了異步操作得到數據,首先布置一件任務,然后返回一個promise對象,該promise對象承諾一定給我一個結果,要是是任務成功將結果返回給我,要么就是任務執行失敗,返回一個異常信息。
所以只要我們有這個promise對象,我們就可以在任何地方處理promise返回給我們的結果,就是這么優雅。換句話說,就是將任務布置的代碼和任務結果的處理代碼進行了分離。
我們來看一個例子
function myReadFile(){ var deferred = Q.defer(); FS.readFile("foo.txt", "utf-8", function (error, text) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(text); } }); return deferred.promise; // 這里返回一個承諾 } /** * many many code here */ promise.then(function(data){ console.log("get the data : "+data); },function(err){ console.err(err); });
好啦,既然知道什么是promise了,我們就開始探討一下q模塊
q模塊的安裝安裝nodejs
node官網下載安裝nodejs
新建一個工程,填寫一基本信息
mkdir qtest && cd qtest && npm init
安裝q模塊
promise的使用npm install q --save
下面是博主在學習q模塊的時候所見所得,可能有所紕漏,如果需要q模塊的全面資料,大家可以參見這里
then 函數我們知道,當得到一個promise以后,我們需要指定對應的處理函數,也就是用then函數來指定對應的處理函數。
then函數傳入兩個函數對象:
當promise被fulfilled的時候執行的函數
當promise被rejected的時候執行的函數
每次只有一個函數可能被執行,因為返回的promise只可能存在一個狀態,要么被promise被解決了,要么promise沒有被解決。
最終then函數返回一個新的promise
如下面所示:
var outputPromise = getInputPromise() .then(function fulfilled_function(input) {// 傳入兩個函數對象,一個用來處理fulfilled情況,一個處理rejected情況 }, function fulfilled_function(reason) { }); // 最終返回一個新的promise
在傳入的fulfilled_function和rejected_function函數中,函數的返回值會影響then函數返回的promise(也就是這里的outputPromise)的行為:
如果返回一個普通值,promise就是fulfilled的
如果拋出一個異常,那么promise就是rejected的
如果返回一個新的promise,那么then函數返回的promise將會被這個新的promise取代。
如果你只關心任務的成功或者失敗狀態,上面的fulfilled_function或者rejected_function可以設置為空。
我們來看幾個例子:
返回一個普通值:
var Q = require("q"); var outputPromise = Q(1).then(function(data){ console.log(data); return 2; // outputPromise將會fulfilled }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 FULFILLED : 2 */
拋出一個異常:
var Q = require("q"); var outputPromise = Q(1).then(function(data){ console.log(data); throw new Error("haha ,error!"); return 2; }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 REJECTED : Error: haha ,error! */
返回一個新的promise
var Q = require("q"); var outputPromise = Q(1).then(function(data){ console.log(data); return Q(3); }); outputPromise.then(function(data){ console.log("FULFILLED : "+data); },function(err){ console.log("REJECTED : "+err); }) /** 運行結果 1 FULFILLED : 3 */流式操作
上面提到過then函數最后會返回一個新的promise,這樣我們就可以將多個promise串聯起來,完成一系列的串行操作。
如下面所示:
return getUsername() .then(function (username) { return getUser(username); }) .then(function (user) { // if we get here without an error, // the value returned here // or the exception thrown here // resolves the promise returned // by the first line });組合操作
假如我們現在有多個任務需要一起并行操作,然后所有任務操作結束后,或者其中一個任務失敗后就直接返回,q模塊的中的all函數就是用來解決這個問題的。
我們來幾個例子
成功執行:
var Q = require("q"); function createPromise(number){ return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.all(promiseArray).then(function(results){ console.log(results); }); /** 運行結果 [ 1, 4, 9, 16, 25 ] */
其中某個拋出異常:
var Q = require("q"); function createPromise(number){ if(number ===3 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.all(promiseArray).then(function(results){ console.log(results); },function (err) { console.log(err); }); /** 運行結果 [Error: haha, error!] */
但是有些時候我們想等到所有promise都得到一個結果以后,我們在對結果進行判斷,看看那些是成功的,那些是失敗的,我們就可以使用allSettled函數。
如下所示:
var Q = require("q"); function createPromise(number){ if(number ===3 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.allSettled(promiseArray) .then(function (results) { results.forEach(function (result) { if (result.state === "fulfilled") { console.log(result.value); } else { console.error(result.reason); } }); }); /** 運行結果 1 4 [Error: haha, error!] 16 25 */
或者有時候我們只需要其中一個promise有結果即可,那么any函數就比較適合我們
如下所示:
var Q = require("q"); function createPromise(number){ if(number ===3 || number === 1 ) return Q(1).then(function(){ throw new Error("haha, error!"); }) return Q(number*number); } var array = [1,2,3,4,5]; var promiseArray = array.map(function(number){ return createPromise(number); }); Q.any(promiseArray).then(function(first){ console.log(first); },function(error){ console.log(error); // all the promises were rejected }); /** 運行結果 4 */Promise的創建
上面我們講到的都是promise的使用,那么如何創建一個新的promise呢,q模塊里面提供好幾種方法來創建一個新的promise:
Using Q.fcall
Using Deferreds
Using Q.promise
Using Q.fcall你可以使用fcall來直接創建一個將會fullfilled的promise:
return Q.fcall(function () { return 10; });
也可以創建一個將會rejected的promise:
return Q.fcall(function () { throw new Error("Can"t do it"); });
或者才創建一個promise的時候傳入自定義參數:
return Q.fcall(function(number1 , number2){ return number1+number2; }, 2, 2);Using Deferreds
上面我們提到的fcall方法來創建promise的方法,雖然簡單,但是在某些時候不一定能滿足我們的需求,比如我們現在創建一個新的promise,需要在某個任務完成以后,讓promise變成fulfilled或者是rejected狀態的,上面的fcall方法就不適合了,因為它是直接返回的。
那么這里使用Deferred來實現這種操作就再合適不過了,我們首先創建一個promise,然后在合適的時候將promise設置成為fulfilled或者rejected狀態的。
如下所示:
var deferred = Q.defer(); FS.readFile("foo.txt", "utf-8", function (error, text) { if (error) { deferred.reject(new Error(error)); } else { deferred.resolve(text); } }); return deferred.promise;Using Q.Promise
最后一個要介紹的就是Q.Promise函數,這個方法其實是和Deferred方法比較類似的,我們要傳入一個帶有三個參數的函數對象,分別是resolve,reject,notify。可以調用這三個函數來設置promise的狀態。
看個例子:
var Q = require("q"); var request = require("request"); function requestUrl(url) { return Q.Promise(function(resolve, reject, notify) { request(url, function (error, response, body) { if(error) reject(error); if (!error && response.statusCode == 200) { resolve(body); } }) }); } requestUrl("http://www.baidu.com").then(function(data){ console.log(data); },function(err){ console.error(err); }); /** 運行結果 百度首頁html內容 */實際例子
這里我們將會模擬串行和并行請求多個url地址。
測試服務器我在本地搭建了一個測試用的express服務器:,對應代碼如下
var express = require("express"); var router = express.Router(); /* GET home page. */ router.get("/address1", function(req, res, next) { res.send("This is address1"); }); router.get("/address2", function(req, res, next) { res.send("This is address2"); }); router.get("/address3", function(req, res, next) { res.send("This is address3"); }); module.exports = router;并行請求
var Q = require("q"); var request = require("request"); var urls = [ "http://localhost:3014/q-test/address1", "http//localhost:3014/q-test/address2", // this is wrong address "http://localhost:3014/q-test/address3" ]; function createPromise(url){ var deferred = Q.defer(); request(url , function(err , response , body){ console.log("requested "+url); if(err) deferred.reject(err); else deferred.resolve(body); }); return deferred.promise; } var promises = urls.map(function (url) { return createPromise(url) ; }); Q.allSettled(promises) .then(function (results) { results.forEach(function (result) { if (result.state === "fulfilled") { console.log(result.value); } else { console.error(result.reason); } }); }); /** 運行結果 requested http//localhost:3014/q-test/address2 requested http://localhost:3014/q-test/address1 requested http://localhost:3014/q-test/address3 This is address1 [Error: Invalid URI "http//localhost:3014/q-test/address2"] This is address3 */串行請求
var Q = require("q"); var request = require("request"); var urls = [ "http://localhost:3014/q-test/address1", "http//localhost:3014/q-test/address2", // this is wrong address "http://localhost:3014/q-test/address3", "done" // append a useless item ]; function createPromise(url){ var deferred = Q.defer(); request(url , function(err , response , body){ if(err) deferred.reject(err); else deferred.resolve(body); }); return deferred.promise; } urls.reduce(function(soFar , url){ return soFar.then(function(data){ if(data) console.log(data); return createPromise(url); } ,function(err){ console.error(err); return createPromise(url); }) },Q(null)); /** 運行結果 requested http://localhost:3014/q-test/address1 This is address1 requested http//localhost:3014/q-test/address2 [Error: Invalid URI "http//localhost:3014/q-test/address2"] requested http://localhost:3014/q-test/address3 This is address3 requested done */延時操作
下面我們使用q模塊來對express服務器進行延時操作:
var express = require("express"); var router = express.Router(); var Q = require("q"); function myDelay(ms){ // 定義延時操作,返回promise var deferred = Q.defer() ; setTimeout(deferred.resolve , ms); return deferred.promise; } /* GET home page. */ router.get("/address1", function(req, res, next) { myDelay(5000).then(function(){ res.send("This is address1"); // 時間到了就返回數據 }); }); router.get("/address2", function(req, res, next) { res.send("This is address2"); }); router.get("/address3", function(req, res, next) { res.send("This is address3"); }); module.exports = router;
好,上面的串行和并行操作我們并沒有看出什么區別,現在我們再來跑一遍程序:
并行結果
/** 運行結果 requested http//localhost:3014/q-test/address2 requested http://localhost:3014/q-test/address3 requested http://localhost:3014/q-test/address1 This is address1 [Error: Invalid URI "http//localhost:3014/q-test/address2"] This is address3 */
串行結果
/** 運行結果 requested http://localhost:3014/q-test/address1 This is address1 requested http//localhost:3014/q-test/address2 [Error: Invalid URI "http//localhost:3014/q-test/address2"] requested http://localhost:3014/q-test/address3 This is address3 requested done */
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79461.html
摘要:編寫異步小爬蟲在通過的課程初步了解的各大模塊之后,不禁感慨于的強大,讓我們這些前端小白也可以進行進階的功能實現,同時發現自己也已經可以通過實現一些比較日常的小功能。 nodejs編寫異步小爬蟲 在通過learnyounode的課程初步了解nodejs的各大模塊之后,不禁感慨于nodejs的強大,讓我們這些前端小白也可以進行進階的功能實現,同時發現自己也已經可以通過nodejs實現一些...
摘要:是一款流式構建系統,如果說是基于任務執行器,就是基于的文件流任務執行器,比起有如下特點使用方便通過代碼優于配置的策略,可以讓簡單的任務簡單,復雜的任務更可管理。 作者:Jogis原文鏈接:https://github.com/yesvods/Blog/issues/1轉載請注明原文鏈接以及作者信息 showImg(http://itanguo.cn/wp-content/uploads...
摘要:中文資料導航官網七牛鏡像深入淺出系列進階必讀中文文檔被誤解的編寫實戰系列熱門模塊排行榜,方便找出你想要的模塊多線程,真正的非阻塞淺析的類利用編寫異步多線程的實例中與的區別管道拒絕服務漏洞高級編程業界新聞看如何評價他們的首次嘗鮮程序員如何說服 node.js中文資料導航 Node.js HomePage Node官網七牛鏡像 Infoq深入淺出Node.js系列(進階必讀) Nod...
摘要:前言是一個微服務工具集,它賦予系統易于連續構建和更新的能力。這個對象既包含某個微服務所需要調取另一個微服務的特征,同時也包含傳參。和微服務發現有些類似不過是用模式代替,目前為止模式是完全可以實現服務發現功能,但是否更加靈活還有待去挖掘。 前言 seneca是一個nodejs微服務工具集,它賦予系統易于連續構建和更新的能力。下面會逐一和大家一起了解相關技術入門以及實踐。 這里插入一段硬廣...
摘要:創建成功后進入文件夾執行執行作用創建文件,維護項目的依賴文件解釋創建文件執行作用用系統的編輯器打開文件。我的技術新群上一篇前后端分離項目實踐分析下一篇公司項目實踐 一、前言 前端如何獨立用nodeJs實現一個簡單的注冊、登錄功能,是不是只用nodejs+sql就可以了?其實是可以實現,但離實際應用還有距離,那要怎么做才是實際可用的。 網上有很多nodeJs的示例,包括和 sql /...
閱讀 1535·2021-09-22 15:35
閱讀 2014·2021-09-14 18:04
閱讀 884·2019-08-30 15:55
閱讀 2457·2019-08-30 15:53
閱讀 2685·2019-08-30 12:45
閱讀 1209·2019-08-29 17:01
閱讀 2584·2019-08-29 15:30
閱讀 3521·2019-08-29 15:09