在 Node.js 中用承诺替换回调
Replacing callbacks with promises in Node.js
我有一个连接到数据库的简单节点模块,它有几个接收数据的函数,例如这个函数:
dbConnection.js:
import mysql from 'mysql';
const connection = mysql.createConnection({
host: 'localhost',
user: 'user',
password: 'password',
database: 'db'
});
export default {
getUsers(callback) {
connection.connect(() => {
connection.query('SELECT * FROM Users', (err, result) => {
if (!err){
callback(result);
}
});
});
}
};
模块将从不同的节点模块以这种方式调用:
app.js:
import dbCon from './dbConnection.js';
dbCon.getUsers(console.log);
我想使用承诺而不是回调来 return 数据。
到目前为止,我已经在以下线程中阅读了有关嵌套承诺的信息:Writing Clean Code With Nested Promises,但我找不到任何对于此用例来说足够简单的解决方案。
return result
使用承诺的正确方法是什么?
设置承诺时需要两个参数,resolve
和 reject
。如果成功,调用 resolve
返回结果,如果失败调用 reject
返回错误。
那你可以这样写:
getUsers().then(callback)
callback
将使用从 getUsers
返回的承诺结果调用,即 result
假设您的数据库适配器 API 不输出 Promises
本身,您可以执行以下操作:
exports.getUsers = function () {
var promise;
promise = new Promise();
connection.connect(function () {
connection.query('SELECT * FROM Users', function (err, result) {
if(!err){
promise.resolve(result);
} else {
promise.reject(err);
}
});
});
return promise.promise();
};
如果数据库 API 确实支持 Promises
你可以做类似的事情:(在这里你看到了 Promises 的力量,你的 回调绒毛 几乎消失)
exports.getUsers = function () {
return connection.connect().then(function () {
return connection.query('SELECT * FROM Users');
});
};
使用 .then()
来 return 一个新的(嵌套的)promise。
通话方式:
module.getUsers().done(function (result) { /* your code here */ });
我为我的 Promise 使用了模型 API,您的 API 可能会有所不同。如果你给我看你的 API 我可以定制它。
以Q库为例:
function getUsers(param){
var d = Q.defer();
connection.connect(function () {
connection.query('SELECT * FROM Users', function (err, result) {
if(!err){
d.resolve(result);
}
});
});
return d.promise;
}
使用 bluebird you can use Promise.promisifyAll
(and Promise.promisify
) 向任何对象添加 Promise 就绪方法。
var Promise = require('bluebird');
// Somewhere around here, the following line is called
Promise.promisifyAll(connection);
exports.getUsersAsync = function () {
return connection.connectAsync()
.then(function () {
return connection.queryAsync('SELECT * FROM Users')
});
};
并像这样使用:
getUsersAsync().then(console.log);
或
// Spread because MySQL queries actually return two resulting arguments,
// which Bluebird resolves as an array.
getUsersAsync().spread(function(rows, fields) {
// Do whatever you want with either rows or fields.
});
添加处理器
Bluebird 支持很多功能,其中之一是处理程序,它允许您在连接结束后在 Promise.using
和 Promise.prototype.disposer
的帮助下安全地处理连接。这是我的应用程序中的示例:
function getConnection(host, user, password, port) {
// connection was already promisified at this point
// The object literal syntax is ES6, it's the equivalent of
// {host: host, user: user, ... }
var connection = mysql.createConnection({host, user, password, port});
return connection.connectAsync()
// connect callback doesn't have arguments. return connection.
.return(connection)
<strong>.disposer(function(connection, promise) {
//Disposer is used when Promise.using is finished.
connection.end();
});</strong>
}
然后像这样使用它:
exports.getUsersAsync = function () {
return <strong>Promise.using(getConnection())</strong>.then(function (connection) {
return connection.queryAsync('SELECT * FROM Users')
});
};</pre>
一旦 promise 使用值解析(或使用 Error
拒绝),这将自动结束连接。
使用 Promise
class
我建议看一下 MDN's Promise docs,它为使用 Promises 提供了一个很好的起点。或者,我相信网上有很多教程。:)
注意:现代浏览器已经支持 ECMAScript 6 Promises 规范(请参阅上面链接的 MDN 文档),我假设您想要使用本机实现,没有第 3 方库.
至于实际例子...
基本原理是这样的:
- 你的API叫
- 你创建了一个新的 Promise 对象,这个对象接受一个函数作为构造函数参数
- 您提供的函数由底层实现调用,该函数有两个函数 -
resolve
和 reject
- 一旦你完成你的逻辑,你就可以调用其中之一来完成承诺或拒绝它并报错
这可能看起来很多,所以这里是一个实际的例子。
exports.getUsers = function getUsers () {
// Return the Promise right away, unless you really need to
// do something before you create a new Promise, but usually
// this can go into the function below
return new Promise((resolve, reject) => {
// reject and resolve are functions provided by the Promise
// implementation. Call only one of them.
// Do your logic here - you can do WTF you want.:)
connection.query('SELECT * FROM Users', (err, result) => {
// PS. Fail fast! Handle errors first, then move to the
// important stuff (that's a good practice at least)
if (err) {
// Reject the Promise with an error
return reject(err)
}
// Resolve (or fulfill) the promise with data
return resolve(result)
})
})
}
// Usage:
exports.getUsers() // Returns a Promise!
.then(users => {
// Do stuff with users
})
.catch(err => {
// handle errors
})
使用 async/await 语言功能 (Node.js >=7.6)
在 Node.js 7.6 中,v8 JavaScript 编译器升级为 async/await support。您现在可以将函数声明为 async
,这意味着它们会自动 return 一个 Promise
,当异步函数完成执行时会解析。在此函数中,您可以使用 await
关键字等待另一个 Promise 解析。
这是一个例子:
exports.getUsers = async function getUsers() {
// We are in an async function - this will return Promise
// no matter what.
// We can interact with other functions which return a
// Promise very easily:
const result = await connection.query('select * from users')
// Interacting with callback-based APIs is a bit more
// complicated but still very easy:
const result2 = await new Promise((resolve, reject) => {
connection.query('select * from users', (err, res) => {
return void err ? reject(err) : resolve(res)
})
})
// Returning a value will cause the promise to be resolved
// with that value
return result
}
Node.js 版本 8.0.0+:
您不必使用 bluebird to promisify the node API methods anymore. Because, from version 8+ you can use native util.promisify:
const util = require('util');
const connectAsync = util.promisify(connection.connectAsync);
const queryAsync = util.promisify(connection.queryAsync);
exports.getUsersAsync = function () {
return connectAsync()
.then(function () {
return queryAsync('SELECT * FROM Users')
});
};
现在,不必使用任何第 3 方库来进行承诺。
以下代码仅适用于 node -v > 8.x
我用这个Promisified MySQL middleware for Node.js
阅读这篇文章Create a MySQL Database Middleware with Node.js 8 and Async/Await
database.js
var mysql = require('mysql');
// node -v must > 8.x
var util = require('util');
// !!!!! for node version < 8.x only !!!!!
// npm install util.promisify
//require('util.promisify').shim();
// -v < 8.x has problem with async await so upgrade -v to v9.6.1 for this to work.
// connection pool https://github.com/mysqljs/mysql [1]
var pool = mysql.createPool({
connectionLimit : process.env.mysql_connection_pool_Limit, // default:10
host : process.env.mysql_host,
user : process.env.mysql_user,
password : process.env.mysql_password,
database : process.env.mysql_database
})
// Ping database to check for common exception errors.
pool.getConnection((err, connection) => {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.error('Database connection was closed.')
}
if (err.code === 'ER_CON_COUNT_ERROR') {
console.error('Database has too many connections.')
}
if (err.code === 'ECONNREFUSED') {
console.error('Database connection was refused.')
}
}
if (connection) connection.release()
return
})
// Promisify for Node.js async/await.
pool.query = util.promisify(pool.query)
module.exports = pool
您必须升级 node -v > 8.x
您必须使用 async 函数才能使用 await。
示例:
var pool = require('./database')
// node -v must > 8.x, --> async / await
router.get('/:template', async function(req, res, next)
{
...
try {
var _sql_rest_url = 'SELECT * FROM arcgis_viewer.rest_url WHERE id='+ _url_id;
var rows = await pool.query(_sql_rest_url)
_url = rows[0].rest_url // first record, property name is 'rest_url'
if (_center_lat == null) {_center_lat = rows[0].center_lat }
if (_center_long == null) {_center_long= rows[0].center_long }
if (_center_zoom == null) {_center_zoom= rows[0].center_zoom }
_place = rows[0].place
} catch(err) {
throw new Error(err)
}
2019:
使用该原生模块 const {promisify} = require('util');
将普通的旧回调模式转换为承诺模式,这样您就可以从 async/await
代码中获益
const {promisify} = require('util');
const glob = promisify(require('glob'));
app.get('/', async function (req, res) {
const files = await glob('src/**/*-spec.js');
res.render('mocha-template-test', {files});
});
我有一个连接到数据库的简单节点模块,它有几个接收数据的函数,例如这个函数:
dbConnection.js:
import mysql from 'mysql';
const connection = mysql.createConnection({
host: 'localhost',
user: 'user',
password: 'password',
database: 'db'
});
export default {
getUsers(callback) {
connection.connect(() => {
connection.query('SELECT * FROM Users', (err, result) => {
if (!err){
callback(result);
}
});
});
}
};
模块将从不同的节点模块以这种方式调用:
app.js:
import dbCon from './dbConnection.js';
dbCon.getUsers(console.log);
我想使用承诺而不是回调来 return 数据。
到目前为止,我已经在以下线程中阅读了有关嵌套承诺的信息:Writing Clean Code With Nested Promises,但我找不到任何对于此用例来说足够简单的解决方案。
return result
使用承诺的正确方法是什么?
设置承诺时需要两个参数,resolve
和 reject
。如果成功,调用 resolve
返回结果,如果失败调用 reject
返回错误。
那你可以这样写:
getUsers().then(callback)
callback
将使用从 getUsers
返回的承诺结果调用,即 result
假设您的数据库适配器 API 不输出 Promises
本身,您可以执行以下操作:
exports.getUsers = function () {
var promise;
promise = new Promise();
connection.connect(function () {
connection.query('SELECT * FROM Users', function (err, result) {
if(!err){
promise.resolve(result);
} else {
promise.reject(err);
}
});
});
return promise.promise();
};
如果数据库 API 确实支持 Promises
你可以做类似的事情:(在这里你看到了 Promises 的力量,你的 回调绒毛 几乎消失)
exports.getUsers = function () {
return connection.connect().then(function () {
return connection.query('SELECT * FROM Users');
});
};
使用 .then()
来 return 一个新的(嵌套的)promise。
通话方式:
module.getUsers().done(function (result) { /* your code here */ });
我为我的 Promise 使用了模型 API,您的 API 可能会有所不同。如果你给我看你的 API 我可以定制它。
以Q库为例:
function getUsers(param){
var d = Q.defer();
connection.connect(function () {
connection.query('SELECT * FROM Users', function (err, result) {
if(!err){
d.resolve(result);
}
});
});
return d.promise;
}
使用 bluebird you can use Promise.promisifyAll
(and Promise.promisify
) 向任何对象添加 Promise 就绪方法。
var Promise = require('bluebird');
// Somewhere around here, the following line is called
Promise.promisifyAll(connection);
exports.getUsersAsync = function () {
return connection.connectAsync()
.then(function () {
return connection.queryAsync('SELECT * FROM Users')
});
};
并像这样使用:
getUsersAsync().then(console.log);
或
// Spread because MySQL queries actually return two resulting arguments,
// which Bluebird resolves as an array.
getUsersAsync().spread(function(rows, fields) {
// Do whatever you want with either rows or fields.
});
添加处理器
Bluebird 支持很多功能,其中之一是处理程序,它允许您在连接结束后在 Promise.using
和 Promise.prototype.disposer
的帮助下安全地处理连接。这是我的应用程序中的示例:
function getConnection(host, user, password, port) {
// connection was already promisified at this point
// The object literal syntax is ES6, it's the equivalent of
// {host: host, user: user, ... }
var connection = mysql.createConnection({host, user, password, port});
return connection.connectAsync()
// connect callback doesn't have arguments. return connection.
.return(connection)
<strong>.disposer(function(connection, promise) {
//Disposer is used when Promise.using is finished.
connection.end();
});</strong>
}
然后像这样使用它:
exports.getUsersAsync = function () { return <strong>Promise.using(getConnection())</strong>.then(function (connection) { return connection.queryAsync('SELECT * FROM Users') }); };</pre>
一旦 promise 使用值解析(或使用
Error
拒绝),这将自动结束连接。
使用 Promise
class
我建议看一下 MDN's Promise docs,它为使用 Promises 提供了一个很好的起点。或者,我相信网上有很多教程。:)
注意:现代浏览器已经支持 ECMAScript 6 Promises 规范(请参阅上面链接的 MDN 文档),我假设您想要使用本机实现,没有第 3 方库.
至于实际例子...
基本原理是这样的:
- 你的API叫
- 你创建了一个新的 Promise 对象,这个对象接受一个函数作为构造函数参数
- 您提供的函数由底层实现调用,该函数有两个函数 -
resolve
和reject
- 一旦你完成你的逻辑,你就可以调用其中之一来完成承诺或拒绝它并报错
这可能看起来很多,所以这里是一个实际的例子。
exports.getUsers = function getUsers () {
// Return the Promise right away, unless you really need to
// do something before you create a new Promise, but usually
// this can go into the function below
return new Promise((resolve, reject) => {
// reject and resolve are functions provided by the Promise
// implementation. Call only one of them.
// Do your logic here - you can do WTF you want.:)
connection.query('SELECT * FROM Users', (err, result) => {
// PS. Fail fast! Handle errors first, then move to the
// important stuff (that's a good practice at least)
if (err) {
// Reject the Promise with an error
return reject(err)
}
// Resolve (or fulfill) the promise with data
return resolve(result)
})
})
}
// Usage:
exports.getUsers() // Returns a Promise!
.then(users => {
// Do stuff with users
})
.catch(err => {
// handle errors
})
使用 async/await 语言功能 (Node.js >=7.6)
在 Node.js 7.6 中,v8 JavaScript 编译器升级为 async/await support。您现在可以将函数声明为 async
,这意味着它们会自动 return 一个 Promise
,当异步函数完成执行时会解析。在此函数中,您可以使用 await
关键字等待另一个 Promise 解析。
这是一个例子:
exports.getUsers = async function getUsers() {
// We are in an async function - this will return Promise
// no matter what.
// We can interact with other functions which return a
// Promise very easily:
const result = await connection.query('select * from users')
// Interacting with callback-based APIs is a bit more
// complicated but still very easy:
const result2 = await new Promise((resolve, reject) => {
connection.query('select * from users', (err, res) => {
return void err ? reject(err) : resolve(res)
})
})
// Returning a value will cause the promise to be resolved
// with that value
return result
}
Node.js 版本 8.0.0+:
您不必使用 bluebird to promisify the node API methods anymore. Because, from version 8+ you can use native util.promisify:
const util = require('util');
const connectAsync = util.promisify(connection.connectAsync);
const queryAsync = util.promisify(connection.queryAsync);
exports.getUsersAsync = function () {
return connectAsync()
.then(function () {
return queryAsync('SELECT * FROM Users')
});
};
现在,不必使用任何第 3 方库来进行承诺。
以下代码仅适用于 node -v > 8.x
我用这个Promisified MySQL middleware for Node.js
阅读这篇文章Create a MySQL Database Middleware with Node.js 8 and Async/Await
database.js
var mysql = require('mysql');
// node -v must > 8.x
var util = require('util');
// !!!!! for node version < 8.x only !!!!!
// npm install util.promisify
//require('util.promisify').shim();
// -v < 8.x has problem with async await so upgrade -v to v9.6.1 for this to work.
// connection pool https://github.com/mysqljs/mysql [1]
var pool = mysql.createPool({
connectionLimit : process.env.mysql_connection_pool_Limit, // default:10
host : process.env.mysql_host,
user : process.env.mysql_user,
password : process.env.mysql_password,
database : process.env.mysql_database
})
// Ping database to check for common exception errors.
pool.getConnection((err, connection) => {
if (err) {
if (err.code === 'PROTOCOL_CONNECTION_LOST') {
console.error('Database connection was closed.')
}
if (err.code === 'ER_CON_COUNT_ERROR') {
console.error('Database has too many connections.')
}
if (err.code === 'ECONNREFUSED') {
console.error('Database connection was refused.')
}
}
if (connection) connection.release()
return
})
// Promisify for Node.js async/await.
pool.query = util.promisify(pool.query)
module.exports = pool
您必须升级 node -v > 8.x
您必须使用 async 函数才能使用 await。
示例:
var pool = require('./database')
// node -v must > 8.x, --> async / await
router.get('/:template', async function(req, res, next)
{
...
try {
var _sql_rest_url = 'SELECT * FROM arcgis_viewer.rest_url WHERE id='+ _url_id;
var rows = await pool.query(_sql_rest_url)
_url = rows[0].rest_url // first record, property name is 'rest_url'
if (_center_lat == null) {_center_lat = rows[0].center_lat }
if (_center_long == null) {_center_long= rows[0].center_long }
if (_center_zoom == null) {_center_zoom= rows[0].center_zoom }
_place = rows[0].place
} catch(err) {
throw new Error(err)
}
2019:
使用该原生模块 const {promisify} = require('util');
将普通的旧回调模式转换为承诺模式,这样您就可以从 async/await
代码中获益
const {promisify} = require('util');
const glob = promisify(require('glob'));
app.get('/', async function (req, res) {
const files = await glob('src/**/*-spec.js');
res.render('mocha-template-test', {files});
});