node.js的Promise库-bluebird示例
前两天公司一哥们写了一段node.js代码发给我,后面特意提了一句“写的不太优雅”。我知道,他意思是回调嵌套回调,因为当时比较急也就没有再纠结。然而内心中总记得要解决这个问题。解决node.js的回调金字塔问题有较多方法,在《深入浅出node.js》这本书中介绍了好几种,有事件发布/订阅模式、Promise模式、async库等。其中Promise模式被很多人推崇,实现的库有很多,本着从众的原则,闭着眼睛选个bluebird吧。
然而bluebird的文档并不咋滴,相当不咋滴!网上的例子基本上都是fs.readFile方法的示例,鲜有其他例子。为了更好的理解和使用bluebird,只能自已动手试一下咯。本文本着实用的目的,主要介绍如何将自定义方法转换为Promise方法,将异步方法转换为同步方法调用。
1. 首先定义一些简单的方法,这是一个很简单例子,模拟读取配置文件、打开数据库、创建数据库结构、创建一个用户、读取这个用户、显示这个用户属性的整个过程。此处就不写node.js的回调嵌套了,以免使用手机打开本文时特别惨不忍睹的。
//数据库对象
var db; //使用配置文件获取连接字符串
var getConn = function(cfg){
} //创建或打开sqlite3数据库
var openDb = function(dbConn){
} //创建数据库结构
var createSchema = function(){
} //创建用户
var createUser = function(){
} //获取用户
var getUser = function(id){
} //显示用户属性
var showUser = function(user){
}
2. 首先来看使用bluebird怎么将异步方法变成同步方法执行
"use strict"; var fs = require("fs");
var sqlite3 = require("sqlite3");
var Promise = require("bluebird"); const conn = "conn.txt";
var db; var getConn = function(cfg){
return new Promise(function(resolve, reject){
fs.readFile(cfg, "utf-8", function(err, data){
if(err){
reject(err);
} else {
console.log("db: ".concat(data));
resolve(data.trim());
}
});
});
} var openDb = function(dbConn){
return new Promise(function(resolve, reject){
db = new sqlite3.Database(dbConn, function(err){
if(err){
reject(err);
} else{
console.log("open database");
resolve();
}
});
});
} var createSchema = function(){
return new Promise(function(resolve, reject){
db.serialize(function(){
var createExpsTable = "CREATE TABLE IF NOT EXISTS expressions ('name' NVARCHAR(20), 'expression' TEXT, 'index' INT, 'likes' INT)";
var createUserTable = "CREATE TABLE IF NOT EXISTS users ('name' NVARCHAR(20), 'password' VARCHAR(20))";
db.exec(createExpsTable, function(err){
if(err){
reject(err);
} else {
console.log("create table expressions");
}
}); db.exec(createUserTable, function(err){
if(err){
reject(err);
} else {
console.log("create table users");
resolve();
}
});
});
});
} var createUser = function(){
return new Promise(function(resolve, reject){
db.run("INSERT INTO users (name, password) VALUES ($name, $password)", {$name: "think8848", $password: "111111"}, function(err){
if(err){
reject(err);
} else{
console.log("createUser");
resolve(this.lastID);
}
});
});
} var getUser = function(id){
return new Promise(function(resolve, reject){
db.get("SELECT rowid, name, password FROM users WHERE rowId = $id", {$id: id}, function(err, row){
if(err){
reject(err);
} else {
console.log("getUser");
resolve(row);
}
});
});
} var showUser = function(user){
console.log("id: ".concat(user.rowid).concat(", name: ").concat(user.name).concat(", password: ").concat(user.password));
} getConn(conn)
.then(openDb)
.then(createSchema)
.then(createUser)
.then(getUser)
.then(showUser)
.catch(function(err){
console.log(err.message);
});
查看执行结果,可以看到完全没有问题,所有方法都按照设想流程在执行。
但是会不会有一种可能,数据太小,电脑执行的很快,所以恰好在下一个方法执行之前上一个方法的异步已经执行完成了(这之前也遇到过这种问题),我们通过 setTimeout 来验证一下:把 createUser 方法延迟1000毫秒再执行,看看 getUser 是否还能获取到数据
var createUser = function(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log("delay 1000ms");
db.run("INSERT INTO users (name, password) VALUES ($name, $password)", {$name: "think8848", $password: "111111"}, function(err){
if(err){
reject(err);
} else{
console.log("createUser");
resolve(this.lastID);
}
});
}, 1000);
});
}
查看执行结果,完全没有问题, getUser 方法并没有偷偷提前执行
3. 在刚开始接触bluebird的时候,我有很多疑问。
其中有一个就是:是否仅需将第一个要执行的异步方法实现为Promise模式,其他的方法只需简单的放到 .then() 方法即可?我们来进行实验一下,这里为了代码结构简单点,我仅演示模拟模拟读取配置文件、打开数据库、创建数据库结构、创建一个用户流程,也很能说明问题了。
"use strict"; var fs = require("fs");
var sqlite3 = require("sqlite3");
var Promise = require("bluebird"); const conn = "conn.txt";
var db; var getConn = function(cfg){
return new Promise(function(resolve, reject){
fs.readFile(cfg, "utf-8", function(err, data){
if(err){
reject(err);
} else {
console.log("db: ".concat(data));
resolve(data.trim());
}
});
});
} var openDb = function(dbConn){
db = new sqlite3.Database(dbConn, function (err) {
if (err) {
throw err;
} else {
console.log("open database");
}
});
} var createSchema = function(){
db.serialize(function () {
var createExpsTable = "CREATE TABLE IF NOT EXISTS expressions ('name' NVARCHAR(20), 'expression' TEXT, 'index' INT, 'likes' INT)";
var createUserTable = "CREATE TABLE IF NOT EXISTS users ('name' NVARCHAR(20), 'password' VARCHAR(20))";
db.exec(createExpsTable, function (err) {
if (err) {
throw err;
} else {
console.log("create table expressions");
}
}); db.exec(createUserTable, function (err) {
if (err) {
throw err;
} else {
console.log("create table users");
}
});
});
} var createUser = function(){
db.run("INSERT INTO users (name, password) VALUES ($name, $password)", { $name: "think8848", $password: "111111" }, function (err) {
if (err) {
throw err;
} else {
console.log("createUser");
}
});
} getConn(conn)
.then(openDb)
.then(createSchema)
.then(createUser)
.catch(function(err){
console.log(err.message);
});
查看执行结果,貌似也没有问题,全部都按照想像中的顺序执行了,是真的吗?
还是再通过 setTimeout 方法验证下,如果将创建数据库结构的时间推迟,是否还能正确创建用户呢?
var createSchema = function(){
setTimeout(function(){
db.serialize(function () {
var createExpsTable = "CREATE TABLE IF NOT EXISTS expressions ('name' NVARCHAR(20), 'expression' TEXT, 'index' INT, 'likes' INT)";
var createUserTable = "CREATE TABLE IF NOT EXISTS users ('name' NVARCHAR(20), 'password' VARCHAR(20))";
db.exec(createExpsTable, function (err) {
if (err) {
throw err;
} else {
console.log("create table expressions");
}
}); db.exec(createUserTable, function (err) {
if (err) {
throw err;
} else {
console.log("create table users");
}
});
});
}, 1000);
}
查看执行结果:出错了,提示没有找到users表,这说明创建用户方法的执行时间要早于创建数据库结构的执行时间。这表明如果要确保每个方法都顺序执行,那就必须每个方法都是Promise模式。
为了更好的看清楚Promise的执行顺序,下面再次用一个简单的例子和运行结果来展示这个问题
"use strict"; var Promise = require("bluebird"); var first = function(){
console.log("first");
}; var second = function(){
console.log("second");
} var third = function(){
console.log("third");
} Promise.resolve().then(first).then(second).then(third);
查看执行结果
修改 second 方法为异步方法
var second = function(){
setTimeout(function () {
console.log("second");
}, 1000);
}
查看执行结果,发现执行顺序已经错了
修改 second 方法为 Promise 方法
var second = function(){
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log("second");
resolve();
},1000);
});
}
查看执行结果,发现顺序又和预期一样了
4. 每个Promise方法都使用这种写法好像有点麻烦,是否有更好的办法呢?在很多bluebird的例子中都给了答案,使用promisify方法,下面我们来看改造后的例子。这里值的一提是的,经实验发现,如果要 promisify 一个方法(这个方法被bluebird官方称之为 nodeFunction ),那么这个方法就必须满足以下签名: function(any arguments..., function callback) nodeFunction ,即:有两个参数,第一个参数是上一个Promise执行后的返回值,第二个参数是回调方法,及时上一个方法没有返回值,那么第一个参数也是不应该省去的。尽可能不要给这个 nodeFunction 方法提供多个参数,如果上一个方法有多个返回值,那么最好将多个返回值封装为一个对象返回。
"use strict"; var fs = require("fs");
var sqlite3 = require("sqlite3");
var Promise = require("bluebird"); const conn = "conn.txt";
var db; var openDb = function(dbConn, callback){
db = new sqlite3.Database(dbConn.trim(), function (err) {
if(!err){
console.log("open database");
}
return callback(err);
});
} var createSchema = function(args, callback){
db.serialize(function () {
var createExpsTable = "CREATE TABLE IF NOT EXISTS expressions ('name' NVARCHAR(20), 'expression' TEXT, 'index' INT, 'likes' INT)";
var createUserTable = "CREATE TABLE IF NOT EXISTS users ('name' NVARCHAR(20), 'password' VARCHAR(20))";
db.exec(createExpsTable, function (err) {
if(err){
callback(err);
}
console.log("create table expressions");
}); db.exec(createUserTable, function (err) {
if (!err) {
console.log("create table users");
}
callback(err);
});
});
} var createUser = function(args, callback){
db.run("INSERT INTO users (name, password) VALUES ($name, $password)", { $name: "think8848", $password: "111111" }, function (err) {
if (!err) {
console.log("createUser");
}
//此处向下一个Promise方法提供参数值
callback(err, this.lastID);
});
} var getUser = function(id, callback){
db.get("SELECT rowid, name, password FROM users WHERE rowId = $id", { $id: id }, function (err, row) {
if (!err) {
console.log("getUser");
}
callback(err, row);
});
} var showUser = function(user){
console.log("id: ".concat(user.rowid).concat(", name: ").concat(user.name).concat(", password: ").concat(user.password));
} var getConnAsync = Promise.promisify(fs.readFile);
var openDbAsync = Promise.promisify(openDb);
var createSchemaAsync = Promise.promisify(createSchema);
var createUserAsync = Promise.promisify(createUser);
var getUserAsync = Promise.promisify(getUser); getConnAsync(conn, 'utf-8')
.then(openDbAsync)
.then(createSchemaAsync)
.then(createUserAsync)
.then(getUserAsync)
.then(showUser)
.catch(function(err){
console.log(err);
});
查看执行结果:完全没有问题,妥妥的按照既定的顺序来了。
为了保险,我们再使用 setTimeout 进行验证
var createUser = function(args, callback){
setTimeout(function () {
console.log("delay 1000ms");
db.run("INSERT INTO users (name, password) VALUES ($name, $password)", { $name: "think8848", $password: "111111" }, function (err) {
if (!err) {
console.log("createUser");
}
//此处向下一个Promise方法提供参数值
callback(err, this.lastID);
});
} ,1000);
}
验证结果:可以看出依旧是按照顺序执行的
我们再看一个例子:
"use strict"; var Promise = require("bluebird"); function first(cb){
var str = "first";
console.log("begin");
cb(null, str);
} function second(data,cb){
var str = "second";
console.log(data);
cb(null, str);
} var firstAsync = Promise.promisify(first);
var secondAsync = Promise.promisify(second); firstAsync().then(secondAsync).then(console.log);
其执行结果如下:
仔细观察我们会发现这个例子中对两个方法使用了promisify方法,按照上面的说明,这两个方法的签应符合 nodeFunction 约定才是,然而第一个方法仅包含一个回调函数参数,并没有包含值参数,我们尝试着加一个:
function first(args, cb){
var str = "first";
console.log("begin");
cb(null, str);
}
执行结果如下:惊讶的发现第一个参数是回调函数,而第二个参数为undefined(此处使用的是vscode的调试功能,毕竟是c#er,感觉vscode还是非常好用)
想都不用想,为 first 方法提供一个 null 参数肯定能解决问题,然而感觉实在还是太奇怪了。
可以尝试用稍优雅点的方法来处理,用一个 Promise.resolve() 空方法前导一下
node.js的Promise库-bluebird示例的更多相关文章
- Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...
- [转]Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例
本文转自:https://www.cnblogs.com/zhongweiv/p/nodejs_koa2_webapp.html 目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装 ...
- Nodejs学习笔记(十五)—Node.js + Koa2 构建网站简单示例
前言 前面一有写到一篇Node.js+Express构建网站简单示例:http://www.cnblogs.com/zhongweiv/p/nodejs_express_webapp.html 这篇还 ...
- Node.js与MongoDB的基本连接示例
Node.js与MongoDB的基本连接示例 前提 已经安装了node.js和MongoDB,本文使用的node.js是v0.12.0,MongoDB是3.0.0. 初始化数据 启动MongoDB服务 ...
- node promise库bluebird
var fs = require('fs') var Promise = require("bluebird") function file1() { return new Pro ...
- 在Node.js使用Promise的方式操作Mysql
最近在学习Node.js,虽然早就听说了回调地狱结果过了一周就遇到了.所以花时间学习了了一下Promise.虽然还有Async/await.co.生成器等选择,但是因为本人基础较差,以及时间问题所以决 ...
- Node.js之Promise维护(同步)多个回调(异步)状态
金天:学习一个新东西,就要持有拥抱的心态,如果固守在自己先前的概念体系,就会有举步维艰的感觉..NET程序员初用node.js最需要适应的就是异步开发, 全是异步,常规逻辑下遍历列表都是异步,如何保证 ...
- Node.js 中开源库探秘 object-assign | 全栈之路
这篇内容呢,讲的是另一个技术栈 Node.js 系列,虽然和咱们这里的主题不是特别吻合,不过嘛,汲取多样性的养分是快速成长的好方法,也是现在流行的全栈工程师的必经之路. 由于这篇内容涉及的是 Node ...
- MongoDB学习(2)—Node.js与MongoDB的基本连接示例
前提 已经安装了node.js和MongoDB,本文使用的node.js是v0.12.0,MongoDB是3.0.0. 初始化数据 启动MongoDB服务,在test数据库中插入一条实例数据: db. ...
随机推荐
- Delphi 7启动后提示Unable to rename delphi32.dro的解决办法
在Win10 64 上安装完Delphi 7,每次打开报错: Unable to rename 'C:\Program Files (x86)\Borland\Delphi7\Bin\delphi32 ...
- Ajax2简单的使用方式
http://www.cnblogs.com/Ming8006/p/6142191.html
- Angular Beijing 发布
为了帮助 Angular 在国内的推广,申请了一个新的域名 www.ngbeijing.cn, 我将 Angular 相关的优秀文章集中在这个站点,欢迎大家访问. 刚刚转载了几篇优秀的文章. Ang ...
- net.sf.json
JSONObject package com.itlwc.test; import net.sf.json.JSONArray; import net.sf.json.JSONO ...
- golang 统计uint64 数字二进制存储中1的数量
package main import ( "fmt") // pc[i] is the population count of i.var pc [256]byte fun ...
- [Unity优化]批处理02:动态批处理
参考链接: https://docs.unity3d.com/Manual/DrawCallBatching.html 原理: cpu每帧把可以进行动态批处理的网格进行合并,再把合并后的数据传给gpu ...
- Machine.config 文件中节点<machineKey>的强随机生成
Machine.config 文件中节点<machineKey>的强随机生成 <machineKey>这个节允许你设置用于加密数据和创建数字签名的服务器特定的密钥.ASP.NE ...
- springboot学习随笔(四):Springboot整合mybatis(含generator自动生成代码)
这章我们将通过springboot整合mybatis来操作数据库 以下内容分为两部分,一部分主要介绍generator自动生成代码,生成model.dao层接口.dao接口对应的sql配置文件 第一部 ...
- python3下获取主流浏览器和python的安装路径
#coding=utf-8#python3下获取主流浏览器和python的安装路径#by dengpeiyou date:2018-07-09import winreg,os #取得浏览器的安装路径d ...
- Abp Area View文件无法调用@L方法
需要再Area中对应的 web.config文件中 修改 <pages pageBaseType="zzz.Web.Views.zzzWebViewPageBase">