Javascript 在 Node.js 中用 promise 替换回调

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/28432401/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 01:46:34  来源:igfitidea点击:

Replacing callbacks with promises in Node.js

javascriptnode.jspromiseqbluebird

提问by Lior Erez

I have a simple node module which connects to a database and has several functions to receive data, for example this function:

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



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

The module would be called this way from a different node module:

该模块将从不同的节点模块以这种方式调用:



app.js:

应用程序.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

I would like to use promises instead of callbacks in order to return the data. So far I've read about nested promises in the following thread: Writing Clean Code With Nested Promises, but I couldn't find any solution that is simple enough for this use case. What would be the correct way to return resultusing a promise?

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

回答by Robert Rossmann

Using the Promiseclass

使用Promise

I recommend to take a look at MDN's Promise docswhich offer a good starting point for using Promises. Alternatively, I am sure there are many tutorials available online.:)

我建议查看MDN 的 Promise 文档,它为使用Promise提供了一个很好的起点。或者,我确定网上有很多教程。:)

Note:Modern browsers already support ECMAScript 6 specification of Promises (see the MDN docs linked above) and I assume that you want to use the native implementation, without 3rd party libraries.

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

As for an actual example...

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

The basic principle works like this:

基本原理是这样的:

  1. Your API is called
  2. You create a new Promise object, this object takes a single function as constructor parameter
  3. Your provided function is called by the underlying implementation and the function is given two functions - resolveand reject
  4. Once you do your logic, you call one of these to either fullfill the Promise or reject it with an error
  1. 您的 API 被调用
  2. 你创建一个新的 Promise 对象,这个对象接受一个函数作为构造函数参数
  3. 您提供的函数由底层实现调用,该函数被赋予两个函数 -resolvereject
  4. 一旦你完成你的逻辑,你就可以调用其中的一个来完成 Promise 或用错误拒绝它

This might seem like a lot so here is an actual example.

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

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

Using the async/await language feature (Node.js >=7.6)

使用 async/await 语言特性 (Node.js >=7.6)

In Node.js 7.6, the v8 JavaScript compiler was upgraded with async/await support. You can now declare functions as being async, which means they automatically return a Promisewhich is resolved when the async function completes execution. Inside this function, you can use the awaitkeyword to wait until another Promise resolves.

在 Node.js 7.6 中,v8 JavaScript 编译器升级为async/await 支持。您现在可以将函数声明为is async,这意味着它们会Promise在异步函数完成执行时自动返回解析。在此函数中,您可以使用await关键字等待另一个 Promise 解析。

Here is an example:

下面是一个例子:

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
}

回答by Madara's Ghost

With bluebirdyou can use Promise.promisifyAll(and Promise.promisify) to add Promise ready methods to any object.

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

And use like this:

并像这样使用:

getUsersAsync().then(console.log);

or

或者

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

Adding disposers

添加处理器

Bluebird supports a lot of features, one of them is disposers, it allows you to safely dispose of a connection after it ended with the help of Promise.usingand Promise.prototype.disposer. Here's an example from my app:

蓝鸟支持很多功能,其中之一是处置器,它可以让你安全地处理一个连接后的帮助下结束了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();
        });
}

Then use it like this:

然后像这样使用它:

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

This will automatically end the connection once the promise resolves with the value (or rejects with an Error).

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

回答by asmmahmud

Node.js version 8.0.0+:

Node.js 版本 8.0.0+:

You don't have to use bluebirdto promisify the node API methods anymore. Because, from version 8+ you can use native util.promisify:

您不必再使用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')
        });
};

Now, don't have to use any 3rd party lib to do the promisify.

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

回答by pery mimon

2019:

2019年:

Use that native module const {promisify} = require('util');to conver plain old callback pattern to promise pattern so you can get benfit from async/awaitcode

使用本机模块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});
});

回答by Halcyon

Assuming your database adapter API doesn't output Promisesitself you can do something like:

假设您的数据库适配器 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();
};

If the database API does support Promisesyou could do something like: (here you see the power of Promises, your callback fluffpretty much disappears)

如果数据库 API 确实支持,Promises您可以执行以下操作:(在这里您可以看到 Promise 的强大功能,您的回调功能几乎消失了)

exports.getUsers = function () {
    return connection.connect().then(function () {
        return connection.query('SELECT * FROM Users');
    });
};

Using .then()to return a new (nested) promise.

使用.then()返回一个新的(嵌套的)承诺。

Call with:

致电:

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


I used a mockup API for my Promises, your API might be different. If you show me your API I can tailor it.

我为我的 Promise 使用了一个模型 API,你的 API 可能会有所不同。如果你向我展示你的 API,我可以定制它。

回答by satchcoder

Using the Q library for example:

例如使用 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;   
}

回答by Tom

When setting up a promise you take two parameters, resolveand reject. In the case of success, call resolvewith the result, in the case of failure call rejectwith the error.

设置承诺时,您需要使用两个参数,resolvereject. 成功时resolve用结果调用,失败时reject用错误调用。

Then you can write:

然后你可以写:

getUsers().then(callback)

callbackwill be called with the result of the promise returned from getUsers, i.e. result

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

回答by hoogw

Below code works only for node -v > 8.x

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

I use this Promisified MySQL middleware for Node.js

我将这个Promisified MySQL 中间件用于 Node.js

read this article Create a MySQL Database Middleware with Node.js 8 and Async/Await

阅读本文使用 Node.js 8 和 Async/Await 创建 MySQL 数据库中间件

database.js

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

You must upgrade node -v > 8.x

您必须升级 node -v > 8.x

you must use async function to be able to use await.

您必须使用 async 函数才能使用 await。

example:

例子:

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