摘要:如果按照上面回调函数的写法这种写法看起来虽然简单,但是如果在各个任务中都含有多个复杂的逻辑操作,需要串行操作的任务一旦变多,那么这种回调的写法就可读性非常差,而且难以维护。
接触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 */
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/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 /...
阅读 1541·2021-09-22 15:35
阅读 2019·2021-09-14 18:04
阅读 894·2019-08-30 15:55
阅读 2462·2019-08-30 15:53
阅读 2691·2019-08-30 12:45
阅读 1211·2019-08-29 17:01
阅读 2591·2019-08-29 15:30
阅读 3525·2019-08-29 15:09