node.js的Promise库-bluebird示例

前两天公司一哥们写了一段node.js代码发给我,后面特意提了一句“写的不太优雅”。我知道,他意思是回调嵌套回调,因为当时比较急也就没有再纠结。然而内心中总记得要解决这个问题。解决node.js的回调金字塔问题有较多方法,在《深入浅出node.js》这本书中介绍了好几种,有事件发布/订阅模式、Promise模式、async库等。其中Promise模式被很多人推崇,实现的库有很多,本着从众的原则,闭着眼睛选个bluebird吧。

然而bluebird的文档并不咋滴,相当不咋滴!网上的例子基本上都是fs.readFile方法的示例,鲜有其他例子。为了更好的理解和使用bluebird,只能自已动手试一下咯。本文本着实用的目的,主要介绍如何将自定义方法转换为Promise方法,将异步方法转换为同步方法调用。

1. 首先定义一些简单的方法,这是一个很简单例子,模拟读取配置文件、打开数据库、创建数据库结构、创建一个用户、读取这个用户、显示这个用户属性的整个过程。此处就不写node.js的回调嵌套了,以免使用手机打开本文时特别惨不忍睹的。

 1 //数据库对象
 2 var db;
 3
 4 //使用配置文件获取连接字符串
 5 var getConn = function(cfg){
 6 }
 7
 8 //创建或打开sqlite3数据库
 9 var openDb =  function(dbConn){
10 }
11
12 //创建数据库结构
13 var createSchema = function(){
14 }
15
16 //创建用户
17 var createUser = function(){
18 }
19
20 //获取用户
21 var getUser = function(id){
22 }
23
24 //显示用户属性
25 var showUser = function(user){
26 }

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() 空方法前导一下

时间: 2024-10-03 13:29:52

node.js的Promise库-bluebird示例的相关文章

Node.js操作Redis的简单示例

Redis是一个key-value类型的数据库,而key全部都是字符串,value可以是集合.hash.list等等. Redis是通过MULTI/DISCARD/EXEC/WATCH这4个命令来实现事务功能.对事务,我们必须知道事务安全性是一个非常重要的. 事务提供了一种"将多个命令打包,然后一次性.按顺序执行"的机制,并且在事务执行期间不会中断--意思就是在事务完成之前,客户端的其他命令都是阻塞状态. var redis = require("redis");

Node.js开发入门—Angular简单示例

在"使用AngularJS"中,我们提到了如何在Node.js项目中引入AngularJS,这次提供一个非常简单的示例,演示AngularJS里的指令.数据绑定.服务等内容. 我准备做Web后台管理系统,不同的管理员会有不同的权限,管理员登录后看到的菜单和他的权限有关,能看到什么,是动态生成的(类似RBAC).本文的示例从这个项目而来,当然,现在还是最简单的. 如果没有特别说明,后面我们用到的示例都使用express generator生成. Angular小demo 先搞起来吧. 第

在Node.js使用Promise的方式操作Mysql

最近在学习Node.js,虽然早就听说了回调地狱结果过了一周就遇到了.所以花时间学习了了一下Promise.虽然还有Async/await.co.生成器等选择,但是因为本人基础较差,以及时间问题所以决定先用好Promise. 你可以选择用原生的,当然最好还是用BlueBird,听说性能比官方的好很多,而且有额外的特性:promisifyAll.Promisify 官方案例: var fs = Promise.promisifyAll(require("fs")); fs.readFil

Node.js中REST API使用示例——基于云平台+云服务打造自己的在线翻译工具

做为一个程序员可能在学习技术,了解行业新动态,解决问题时经常需要阅读英文的内容:而像我这样的英文小白就只能借助翻译工具才能理解个大概:不禁经常感慨,英文对学习计算机相关知识太重要了!最近发现IBM的云平台Blumemix,并且提供语言翻译的服务,感觉不错,就拿来研究学习一下:这里就分享一下我的研究学习过程,如何使用Node.js调用REST API打造自己的在线翻译工具,并演示如何把它发布到云平台上,让每个人都可以通过网络访问使用它. 应用效果展示 您可以通过点击效果图片的链接访问它. 构建一个

Node.js之Promise维护(同步)多个回调(异步)状态

金天:学习一个新东西,就要持有拥抱的心态,如果固守在自己先前的概念体系,就会有举步维艰的感觉..NET程序员初用node.js最需要适应的就是异步开发, 全是异步,常规逻辑下遍历列表都是异步,如何保证列表遍历执行完毕?Promise帮你搞定!金天微信:15998603918 欢迎找我聊聊天. Node.js编程,清一色的回调. 如果没有Promise, 一连串的业务逻辑,从第一步回调到最后一步,“单线程逻辑”还搞的定,遭遇“多线程逻辑”,便陷入回调地狱. 自从有了Promise, 一切都不在是问

Node.js(十三)——Promise重构爬虫代码

在重构代码之前,先要了解下什么是https? https协议:基于ssl/tls的http协议,所有的数据都是在 ssl/tls协议的封装之上传输的,也就是说https协议是在http协议基础上 添加了ssl/tls握手以及数据加密传输,因此这就是两者之间最大的区别. https模块专门处理加密访问的,区别在于搭建https服务器的时候需要有ssl证书. 模拟搭建https服务器 var https = require('https') var fs = require('fs')//文件系统模

MongoDB学习(2)—Node.js与MongoDB的基本连接示例

前提 已经安装了node.js和MongoDB,本文使用的node.js是v0.12.0,MongoDB是3.0.0. 初始化数据 启动MongoDB服务,在test数据库中插入一条实例数据: db.user.install({name:"scaleworld",age:27}); 在Node.js中引入MongoDB模块 npm install mongodb 编写mongodbDemo.js var mongodb = require('mongodb'); var server

Nodejs学习笔记(七)--- Node.js + Express 构建网站简单示例

目录 前言 新建项目.建立数据库以及其它准备工作 新建express + ejs 项目:sampleEjs 创建数据库 修改package.json文件,安装session和mysql模块 样式和JQuery文件 清理项目冗余文件,并添加监听 规划路由,并新建相关文件 实现登录和注册需要的数据访问方法 注册 登录 首页 安全退出 写在之后 前言 上一篇学习了一些构建网站会用到的一些知识点   http://www.cnblogs.com/zhongweiv/p/nodejs_express_we

when 让你跳出异步回调噩梦 node.js下promise/A规范的使用

其实关于promise 的博客,前端时间专门写了一篇关于 promise 规范的文章,promise规范 让 javascript 中的异步调用更加人性化. 简单回忆下: promise/A规范定义的“Deferred/Promise”模型 其实是实现了“发布/订阅”模型. 通过Deferred对象发布事件, 包括下面2种事件 完成 --> resolve事件, 失败 --> reject事件, 通过Promise对象进行对应完成或失败的订阅 类似于任务状态转变时事件被触发. 将会对应的执行事