用Node.js中的promises替换回调

时间:2015-02-10 13:03:52

标签: javascript node.js promise q bluebird

我有一个简单的节点模块,它连接到一个数据库并有几个接收数据的函数,例如这个函数:


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);

我想使用promises而不是回调来返回数据。 到目前为止,我已经在下面的帖子中读到了嵌套的promise:Writing Clean Code With Nested Promises,但我找不到任何对这个用例来说足够简单的解决方案。 使用承诺返回result的正确方法是什么?

8 个答案:

答案 0 :(得分:94)

使用Promise

我建议您查看MDN's Promise docs,它为使用Promise提供了一个很好的起点。或者,我确信在线提供了许多教程。:)

注意:现代浏览器已经支持Promises的ECMAScript 6规范(请参阅上面链接的MDN文档),我假设您希望使用本机实现,而不使用第三方库。

至于一个实际的例子......

基本原理是这样的:

  1. 您的API名为
  2. 您创建一个新的Promise对象,此对象将一个函数作为构造函数参数
  3. 您提供的函数由底层实现调用,函数有两个函数 - resolvereject
  4. 完成逻辑操作后,您可以调用其中一个来填充Promise或拒绝错误
  5. 这可能看起来很多,所以这是一个实际的例子。

    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中,使用async/await support升级了v8 JavaScript编译器。您现在可以将函数声明为async,这意味着它们会自动返回Promise,当异步函数完成执行时,该await将被解析。在此函数中,您可以使用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 } 关键字等待另一个Promise结算。

    以下是一个例子:

    {{1}}

答案 1 :(得分:31)

使用 bluebird ,您可以使用Promise.promisifyAll(和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.usingPromise.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) 
        .disposer(function(connection, promise) { 
            //Disposer is used when Promise.using is finished.
            connection.end();
        });
}

然后像这样使用它:

exports.getUsersAsync = function () {
    return Promise.using(getConnection()).then(function (connection) {
            return connection.queryAsync('SELECT * FROM Users')
        });
};

一旦承诺与值一起解析(或使用Error拒绝),这将自动结束连接。

答案 2 :(得分:9)

Node.js版本8.0.0 +:

您不必再使用bluebird来宣传节点API方法。因为,从版本8+开始,您可以使用原生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')
        });
};

现在,不必使用任何第三方库来进行promisify。

答案 3 :(得分:3)

假设您的数据库适配器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()返回新的(嵌套)承诺。

致电:

module.getUsers().done(function (result) { /* your code here */ });

我为我的Promises使用了一个模型API,你的API可能会有所不同。如果你告诉我你的API,我可以定制它。

答案 4 :(得分:2)

在设置承诺时,您需要使用两个参数resolvereject。如果成功,请使用结果调用resolve,如果失败则调用reject并显示错误。

然后你可以写:

getUsers().then(callback)
将使用callback返回的承诺结果调用

getUsers,即result

答案 5 :(得分:2)

以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;   
}

答案 6 :(得分:1)

2019:

使用该本机模块const {promisify} = require('util');将普通的旧回调模式转换为Promise模式,以便您可以从async/await代码中受益

const {promisify} = require('util');
const glob = promisify(require('glob'));

app.get('/', async function (req, res) {
    var glob = promisify(require('glob'));
    const files = await glob('src/**/*-spec.js');
    res.render('mocha-template-test', {files});
});

答案 7 :(得分:0)

以下代码仅适用于节点-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

您必须升级节点-v&gt; 8.x

您必须使用异步功能才能使用等待。

示例:

   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)
       }