【问题标题】:Replacing callbacks with promises in Node.js在 Node.js 中用 Promise 替换回调
【发布时间】:2015-04-10 12:40:53
【问题描述】:

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


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

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

【问题讨论】:

  • 如果您使用的是 kriskowal 的 Q 库,请参阅 Adapting Node
  • @leo.249:你读过 Q 文档吗?您是否已经尝试将其应用于您的代码 - 如果是,请发布您的尝试(即使不工作)?你到底卡在哪里了?您似乎找到了一个不简单的解决方案,请发布。
  • @leo.249 Q 几乎没有维护——最后一次提交是 3 个月前。 Q 开​​发人员只对 v2 分支感兴趣,而且它甚至还没有接近生产就绪。从 10 月开始,问题跟踪器中有未解决的问题,但没有 cmets。我强烈建议你考虑一个维护良好的 Promise 库。

标签: javascript node.js promise


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    以下代码仅适用于 node -v > 8.x

    我用这个Promisified MySQL middleware for Node.js

    阅读这篇文章Create a MySQL Database Middleware with Node.js 8 and Async/Await

    数据库.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)
           }
    

    【讨论】:

      【解决方案3】:

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

      现在,不必使用任何 3rd 方库来进行承诺。

      【讨论】:

        【解决方案4】:

        使用Promise

        我建议看一下MDN's Promise docs,它为使用 Promises 提供了一个很好的起点。或者,我相信网上有很多教程。:)

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

        举个实际的例子……

        基本原理是这样的:

        1. 您的 API 被调用
        2. 您创建一个新的 Promise 对象,该对象将单个函数作为构造函数参数
        3. 您提供的函数由底层实现调用,该函数有两个函数 - resolvereject
        4. 执行逻辑后,您可以调用其中一个来完成 Promise 或以错误拒绝它

        这可能看起来很多,所以这里是一个实际的例子。

        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,这意味着它们会自动返回一个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
        }
        

        【讨论】:

        • Promises 是 ECMAScript 2015 规范的一部分,Node v0.12 使用的 v8 提供了规范这一部分的实现。所以是的,它们不是 Node 核心的一部分——它们是语言的一部分。
        • 很高兴知道,我的印象是要使用 Promises,您需要安装一个 npm 包并使用 require()。我在 npm 上找到了实现裸骨/A++ 风格的 Promise 包,并使用了它,但对于节点本身(不是 JavaScript)仍然是新手。
        • 这是我最喜欢的编写 Promise 和架构异步代码的方式,主要是因为它是一种一致的模式,易于阅读,并且允许高度结构化的代码。
        【解决方案5】:

        使用 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')
                });
        };

        一旦 promise 以该值解析(或以 Error 拒绝),这将自动结束连接。

        【讨论】:

        • 很好的答案,感谢你,我最终使用了 bluebird 而不是 Q,谢谢!
        • 请记住,使用 Promise 表示您同意在每次通话时使用 try-catch。因此,如果您经常这样做,并且您的代码复杂性与示例相似,那么您应该重新考虑这一点。
        【解决方案6】:

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

        【讨论】:

        • 会,否则 { d.reject(new Error(err)); },解决这个问题?
        【解决方案7】:

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

        致电:

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

        我为我的 Promises 使用了一个模型 API,您的 API 可能会有所不同。如果您向我展示您的 API,我可以对其进行定制。

        【讨论】:

        • 什么 promise 库有 Promise 构造函数和 .promise() 方法?
        • 谢谢。我只是在练习一些 node.js,我发布的就是它的全部内容,非常简单的示例来弄清楚如何使用 Promise。您的解决方案看起来不错,但我必须安装什么 npm 包才能使用 promise = new Promise();
        • 虽然您的 API 现在返回一个 Promise,但您还没有摆脱厄运金字塔,也没有举例说明 Promise 如何替代回调。
        • @leo.249 我不知道,任何符合 Promises/A+ 的 Promise 库都应该是好的。请参阅:promisesaplus.com/@Bergi 这无关紧要。 @SecondRikudo 如果您与之交互的 API 不支持 Promises,那么您将无法使用回调。一旦你进入承诺领域,“金字塔”就会消失。请参阅第二个代码示例,了解其工作原理。
        • @Halcyon 查看我的回答。即使是使用回调的现有 API 也可以“承诺”为 Promise 就绪的 API,从而使代码更加简洁。
        【解决方案8】:

        在设置 Promise 时,您需要两个参数,resolvereject。成功时调用resolve返回结果,失败时调用reject返回错误。

        然后你可以写:

        getUsers().then(callback)
        

        callback 将使用从getUsers 返回的承诺结果调用,即result

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-05-10
          • 2023-03-04
          • 1970-01-01
          • 2015-07-26
          • 2013-06-16
          • 2017-03-27
          • 2016-08-11
          • 2020-12-18
          相关资源
          最近更新 更多