打仗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
模块不单单议是为了处理回调地狱的题目,它还能很大程度上辅佐你举行一些须要并行,串行,定时等操纵。
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
模块
q模块的装置
装置nodejs
node官网下载装置
nodejs
新建一个工程,填写一基本信息
mkdir qtest && cd qtest && npm init
装置q
模块
npm install q –save
promise的运用
下面是博主在进修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
*/