当前位置: 首页 > 工具软件 > Q.js > 使用案例 >

NodeJS的Q入门指导(一)

夹谷晋
2023-12-01

用Q来替代回调函数是编写高质量,易维护的NodeJS代码的方法之一。然后Q的官方指导是英文,并且没有针对性的实例,很不利于初学者掌握。因此我花了两天功夫,把官方的(https://github.com/kriskowal/q)教程按照我的理解重新组织,翻译了一下。并且给出了大量的实例代码,供初学者参考。
1.基本介绍
Promise有个then方法,then方法可以接受3个函数作为参数。前两个函数对应Promise的两种状态的回调函数fulfiled 和 rejected,第三个函数用于处理进度信息。
promiseSomething().then(function(fulfiled){
		//当promise状态变成fulfiled时,调用此函数
	},function(rejected){
		//当promise状态变成rejected时,调用此函数
	},function(progress){
		//当返回进度信息时,调用此函数
	})


2.Promise传递

then方法会返回一个promise,在下面这个例子中,我们用outputPromise指向then返回的promise。

var outputPromise = getInputPromise().then(function(fulfiled){
	},function(rejected){
	});
现在outputPromise就变成了受function(fulfiled)或者function(rejected)控制状态的promise了。怎么理解这句话呢?

1) 当function(fulfiled)或者function(rejected)返回一个值,比如一个字符串,数组,对象等等,那么outputPromise的状态就会变成fulfiled。
    在下面这个例子中,我们可以看到,当我们把inputPromise的状态通过defer.resovle()变成fulfiled时,控制台输出fulfiled. 
    当我们把inputPromise的状态通过defer.reject()变成rejected,控制台输出rejected

/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,调用function(fulfiled)
	 * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
	 * 将then返回的promise赋给outputPromise
	 * function(fulfiled) 和 function(rejected) 通过返回字符串将outputPromise的状态由
	 * 未完成改变为fulfiled
	 * @private
	 */
	var outputPromise = getInputPromise().then(function(fulfiled){
		return 'fulfiled';
	},function(rejected){
		return 'rejected';
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'fulfiled'。
	 * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'rejected'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	defer.reject();

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	//defer.resolve();

2). 当function(fulfiled)或者function(rejected)抛出异常时,那么outputPromise的状态就会变成rejected 

var Q = require('q');
	var fs = require('fs');
	var defer = Q.defer();

	/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,调用function(fulfiled)
	 * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
	 * 将then返回的promise赋给outputPromise
	 * function(fulfiled) 和 function(rejected) 通过抛出异常将outputPromise的状态由
	 * 未完成改变为reject
	 * @private
	 */
	var outputPromise = getInputPromise().then(function(fulfiled){
		throw new Error('fulfiled');
	},function(rejected){
		throw new Error('rejected');
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'[Error:fulfiled]'。
	 * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'[Error:rejected]'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	defer.reject();

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	//defer.resolve();
3). 当function(fulfiled)或者function(rejected)返回一个promise时,outputPromise就会成为这个新的promise. 这样做有什么意义呢? 主要在于聚合结果(Q.all),管理延时,异常   恢复等等

    比如说我们想要读取一个文件的内容,然后把这些内容打印出来。可能会写出这样的代码:

var outputPromise = getInputPromise().then(function(fulfiled){
			fs.readFile('test.txt','utf8',function(err,data){
				return data;
			});
			},function(rejected){
			throw new Error('rejected');
			});

 然后由于异步IO的原因,这样写是不行的。需要下面的方式:

var Q = require('q');
	var fs = require('fs');
	var defer = Q.defer();

	/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,调用function(fulfiled)
	 * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
	 * 将then返回的promise赋给outputPromise
	 * function(fulfiled)将新的promise赋给outputPromise
	 * 未完成改变为reject
	 * @private
	 */
	var outputPromise = getInputPromise().then(function(fulfiled){
		var myDefer = Q.defer();
		fs.readFile('test.txt','utf8',function(err,data){
			if(!err && data) {
				myDefer.resolve(data);
			}
		});
		return myDefer.promise;
	},function(rejected){
		throw new Error('rejected');
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'[Error:fulfiled]'。
	 * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'[Error:rejected]'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	//defer.reject();

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	defer.resolve();

如果then方法中提供的参数不完整会怎么样呢?

1) 没有提供function(rejected)

var outputPromise = getInputPromise().then(function(fulfiled){})
如果inputPromise的状态由未完成变成rejected, 此时对rejected的处理会由outputPromise来完成。

var Q = require('q');
	var fs = require('fs');
	var defer = Q.defer();

	/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,调用function(fulfiled)
	 * 当inputPromise状态由未完成变成rejected时,这个rejected会传向outputPromise
	 * function(fulfiled)将新的promise赋给outputPromise
	 * 未完成改变为reject
	 * @private
	 */
	var outputPromise = getInputPromise().then(function(fulfiled){
		return 'fulfiled'
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'[Error:fulfiled]'。
	 * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'[Error:rejected]'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	defer.reject('inputpromise rejected');

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	//defer.resolve();

2).没有提供function(fulfiled)

var outputPromise = getInputPromise().then(null,function(rejected){})
如果inputPromise的状态由未完成变成fulfiled, 此时对fulfil的处理会由outputPromise来完成。
var Q = require('q');
	var fs = require('fs');
	var defer = Q.defer();

	/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,传递给outputPromise
	 * 当inputPromise状态由未完成变成rejected时,调用function(rejected)
	 * function(fulfiled)将新的promise赋给outputPromise
	 * 未完成改变为reject
	 * @private
	 */
	var outputPromise = getInputPromise().then(null,function(rejected){
		return 'rejected';
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'[Error:fulfiled]'。
	 * 当outputPromise状态由未完成变成rejected, 调用function(rejected), 控制台打印'[Error:rejected]'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	//defer.reject('inputpromise rejected');

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	defer.resolve('inputpromise fulfiled');
3). 可以使用fail(function(error))来专门针对错误处理,而不是使用then(null,function(error))

var outputPromise = getInputPromise().fail(function(error){})
看这个例子

var Q = require('q');
	var fs = require('fs');
	var defer = Q.defer();

	/**
	 * 通过defer获得promise
	 * @private
	 */
	function getInputPromise() {
		return defer.promise;
	}

	/**
	 * 当inputPromise状态由未完成变成fulfil时,调用then(function(fulfiled))
	 * 当inputPromise状态由未完成变成rejected时,调用fail(function(error))
	 * function(fulfiled)将新的promise赋给outputPromise
	 * 未完成改变为reject
	 * @private
	 */
	var outputPromise = getInputPromise().then(function(fulfiled){
		return fulfiled;
	}).fail(function(error){
		return error;
	});

	/**
	 * 当outputPromise状态由未完成变成fulfil时,调用function(fulfiled),控制台打印'inputpromise fulfiled'。
	 * 当outputPromise状态由未完成变成rejected, 调用fail(function(rejected)), 控制台打印'inputpromise rejected'。
	 */
	outputPromise.then(function(fulfiled){
		console.log(fulfiled);
	},function(rejected){
		console.log(rejected);
	});

	/**
	 * 将inputPromise的状态由未完成变成rejected
	 */
	//defer.reject('inputpromise rejected');

	/**
	 * 将inputPromise的状态由未完成变成fulfiled
	 */
	defer.resolve('inputpromise fulfiled');
3. Promise链

promise有两种链接方式,一种是在handler(promise回调函数)内部链接,一种是在handler外部链接

 1) 内部链接

return getUsername()
	.then(function(username){
		return getUser(username)
		.then(function(user){
		//Do stuff here
		})
	})
2) 外部链接
return getUsername()
	.then(function(username){
		return getUser(username);
	})
	.then(function(user){
	 // Do stuff here
	})

 内部链接和外部链接有什么区别吗? 唯一的区别就是内部链接可以通过闭包的方式拿到多个输入值

return getUsername()
   .then(function(username){
	 return getUser(username)
   })
   .then(function(user){
	 return getPassword()
	 .then(function(password){
		if(user.passwordHash !== hash(password)){
			throw new Error('can;t authenticate');
		}
	 })
   })
  我本人比较烦这种只给一个伪代码框架,没有具体实现的例子。现在我就来把上面的三个伪代码框架具体实现以下,以便更好地体会promise链。用数据库就太麻烦了,偷个懒,用数组来代替数据库。

 var Q = require('q');
	var defer = Q.defer();
	
	//一个模拟数据库
	var users = [{'name':'andrew','passwd':'password'}];
	
	function getUsername() {
	return defer.promise;
	}

	function getUser(username){
		var user;
		users.forEach(function(element){
			if(element.name === username) {
				user = element;
			}
		});
		return user;
	}

    //内部链接
	//Todo 不能工作,需要查找原因
	getUsername().then(function(username){
		return getUser(username)
		.then(function(user){
			console.log(user)
		})
	});

	//外部链接
	getUsername().then(function(username){
	 return getUser(username);
	}).then(function(user){
	 console.log(user);
	});

	defer.resolve('andrew');

如果有多个需要顺利执行的方法,我们可以手动的把他们链接起来
比如 foo(initialVal).then(bar).then(baz).then(qux)
当然,也可以用代码的方式动态组合链
	var funcs = [foo,bar,baz,qux]
		var result = Q(initialVal);
		funcs.forEach(function(fuc){
			result = result.then(fuc);
		})
		return result;
更为精简一些

funcs.reduce(function(pre,current){
			return pre.then(current);
		},Q(initalVal))

同样,我们来看具体的例子
				function foo(result) {
					console.log(result);
					return result+result;
				}
				//手动链接
				Q('hello').then(foo).then(foo).then(foo);

				//动态链接
				var funcs = [foo,foo,foo];
				var result = Q('hello');
				funcs.forEach(function(func){
					result = result.then(func);
				});
				//精简后的动态链接
				funcs.reduce(function(prev,current){
					return prev.then(current);
				},Q('hello'));	

4.组合

我们可以通过Q.all([promise1,promise2..])将多个promise组合成一个promise.
注意,
 1) 当all里面所有的promise都fulfil时,Q.all返回的promise状态变成fulfil
 2) 当任意一个promisereject时,Q.all返回的promise状态立即变成reject 
 当所有的promise都fulfil时:

	 var Q = require('q');
	/*
	 *获取新的promise,timeout用于控制promise状态转换的时间
	 *@private
	 */
	function getPromise(msg,timeout) {
		var defer = Q.defer();
		setTimeout(function(){
			defer.resolve(msg);
		},timeout);
		return defer.promise;
	}
	/**
	 *虽然promise1在1秒后就进入fulfiled状态,但是Q.all会等待所有的promise都在fulfil状态后,才会转换成fulfil状态
	 *因此,控制台会在两秒后才打印
	 */

	Q.all([getPromise('promise1',1000),getPromise('promise2',2000)])
		.then(function(result){
			result.forEach(function(element){
				if(Q.isPromise(element)) {
					console.log(element.valueOf());
				}else {
					console.log(element)
				}
			});
		});

当某个promise状态时rejected时:

			  var Q = require('q');
			/*
			 *获取新的promise,timeout用于控制promise状态转换的时间
			 *@private
			 */
			function getPromise(msg,timeout,opt) {
				var defer = Q.defer();
				setTimeout(function(){
					if(opt)
						defer.reject(msg);
					else
						defer.resolve(msg);
				},timeout);
				return defer.promise;
			}
			/**
			 *promise1在1秒后进入rejected状态, promise2在2秒后进入rejected状态
			 *Q.all在第一个进入rejected状态的promise后,也进入rejected状态. 
			 *所以控制台打印 promise1 reject
			 */
			Q.all([getPromise('promise1 reject',1000,'reject'),getPromise('promise2 reject',2000,'reject')])
				.then(function(result){
					console.log(result);
				})
				.fail(function(result){
					console.log(result);
				});

现在知道Q.all会在任意一个promise进入reject状态后立即进入reject状态。如果我们需要等到所有的promise都发生状态后(有的fulfil, 有的reject),再转换Q.all的状态, 这时我们可以使用Q.allSettled

		var Q = require('q');
		/*
		 *获取新的promise,timeout用于控制promise状态转换的时间
		 *@private
		 */
		function getPromise(msg,timeout,opt) {
			var defer = Q.defer();
			setTimeout(function(){
				if(opt)
					defer.reject(msg);
				else
					defer.resolve(msg);
			},timeout);
			return defer.promise;
		}
		/**
		 *promise1在1秒后进入fulfil状态, promise2在2秒后进入rejected状态
		 *Q.allSettled等待所有的promise完成状态转换 
		 *所以控制台打印 fulfiled rejected
		 */
		Q.allSettled([getPromise('promise1 resovle',1000),getPromise('promise2 reject',2000,'reject')])
			.then(function(result){
				result.forEach(function(element){
					console.log(element.state);
				});
			})
			.fail(function(result){
				console.log(result);
			});
spread还是then:简单说来,当传给Q.all的promise数量较少时,比如2-3个,可以使用spread。然而当promise较多时,还是通过then更为现实

spread和then的区别请看下面的示例

			var Q = require('q');
			/*
			 *获取新的promise,timeout用于控制promise状态转换的时间
			 *@private
			 */
			function getPromise(msg,timeout,opt) {
				var defer = Q.defer();
				setTimeout(function(){
					if(opt)
						defer.reject(msg);
					else
						defer.resolve(msg);
				},timeout);
				return defer.promise;
			}
			/**
			 *promise1在1秒后进入fulfil状态, promise2在2秒后进入fulfil状态
			 *Q.allSettled等待所有的promise完成状态转换 
			 *所以控制台打印 fulfiled rejected
			 */
			Q.all([getPromise('promise1 resovle',1000),getPromise('promise2 reject',2000)])
				//使用spread 注意spread传入的值和Q.all里的promise一一对应
				.spread(function(pro1,pro2){
					console.log(pro1);
					console.log(pro2);
				})
				//使用then
				.then(function(result){
					result.forEach(function(element){
						if(Q.isPromise(element)) {
							console.log(element.valueOf());
						}else {
							console.log(element)
						}
					});
				})
				.fail(function(result){
					console.log(result);
				});

5.结束

通常,对于一个promise链,有两种结束的方式。第一种方式是返回最后一个promise
如 return foo().then(bar);
第二种方式就是通过done来结束promise链
如 foo().then(bar).done()
为什么需要通过done来结束一个promise链呢? 如果在我们的链中有错误没有被处理,那么在一个正确结束的promise链中,这个没被处理的错误会通过异常抛出。

					var Q = require('q');
					/**
					 *@private
					 */
					function getPromise(msg,timeout,opt) {
						var defer = Q.defer();
						setTimeout(function(){
						console.log(msg);
							if(opt)
								defer.reject(msg);
							else
								defer.resolve(msg);
						},timeout);
						return defer.promise;
					}
					/**
					 *没有用done()结束的promise链
					 *由于getPromse('2',2000,'opt')返回rejected, getPromise('3',1000)就没有执行
					 *然后这个异常并没有任何提醒,是一个潜在的bug
					 */
					getPromise('1',3000)
							.then(function(){return getPromise('2',2000,'opt')})
							.then(function(){return getPromise('3',1000)});
					/**
					 *用done()结束的promise链
					 *有异常抛出
					 */
					getPromise('1',3000)
							.then(function(){return getPromise('2',2000,'opt')})
							.then(function(){return getPromise('3',1000)})
							.done();



 类似资料: