分享web开发知识

注册/登录|最近发布|今日推荐

主页 IT知识网页技术软件开发前端开发代码编程运营维护技术分享教程案例
当前位置:首页 > 运营维护

node.js的Promise库-bluebird示例

发布时间:2023-09-06 02:27责任编辑:苏小强关键词:js

原文地址:https://www.cnblogs.com/think8848/p/6591238.html

前两天公司一哥们写了一段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 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 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示例

原文地址:https://www.cnblogs.com/caiyt/p/10165895.html

知识推荐

我的编程学习网——分享web前端后端开发技术知识。 垃圾信息处理邮箱 tousu563@163.com 网站地图
icp备案号 闽ICP备2023006418号-8 不良信息举报平台 互联网安全管理备案 Copyright 2023 www.wodecom.cn All Rights Reserved