接触nodejs
时间不长,若是有所纰漏,请你们批评指正javascript
q
众所周知,nodejs
是异步的,可是何为异步呢?就是设置一个任务后当即返回,而后加上一个监听,当任务结束的时候,就去调用监听。html
好比下面的代码:java
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
要是对于简单的任务,这是一个很是好的设计,给你一个任务,任务作好了立刻告诉我,而后触发相应的操做node
可是若是对于复杂的任务,就未必是一种好的作法了。好比咱们如今有多个任务须要进行处理,可是这些任务之间存在以来关系,好比任务二须要在任务一完成之后才能够开始,任务三要在任务二后面才能够开始。。。若是按照上面回调函数的写法:git
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 }); }); }); });
这种写法看起来虽然简单,可是若是在各个任务中都含有多个复杂的逻辑操做,须要串行操做的任务一旦变多,那么这种回调的写法就可读性很是差,并且难以维护。也就是所谓的回调地狱。github
拿有没有什么方法能够简化这种复杂的操做呢?答案是确定的,那就是nodejs
里面的q
模块。express
q
模块不只仅是为了解决回调地狱的问题,它还能很大程度上辅助你进行一些须要并行,串行,定时等操做。npm
promise
是用来取代回调函数来进行异步操做的另外一种方案promise
咱们先来看一下大牛对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
模块
安装nodejs
node官网下载安装
nodejs
新建一个工程,填写一基本信息
mkdir qtest && cd qtest && npm init
安装q
模块
npm install q --save
下面是博主在学习q
模块的时候所见所得,可能有所纰漏,若是须要q
模块的全面资料,你们能够参见这里
咱们知道,当获得一个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
呢,q
模块里面提供好几种方法来建立一个新的promise
:
Using Q.fcall
Using Deferreds
Using Q.promise
你可使用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);
上面咱们提到的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;
最后一个要介绍的就是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 */