簡體   English   中英

在 Node.js 中用 promise 替換回調

[英]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);
        }
      });
    });
  }
};

該模塊將從不同的節點模塊以這種方式調用:


應用程序.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

我想使用承諾而不是回調來返回數據。 到目前為止,我已經在以下線程中閱讀了有關嵌套 Promise 的內容: Writing Clean Code With Nested Promises ,但我找不到任何對於此用例足夠簡單的解決方案。 使用承諾返回result的正確方法是什么?

使用Promise

我建議查看MDN 的 Promise 文檔,它為使用Promise提供了一個很好的起點。 或者,我確定網上有很多教程。:)

注意:現代瀏覽器已經支持 Promise 的 ECMAScript 6 規范(請參閱上面鏈接的 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 支持 您現在可以將函數聲明為async ,這意味着它們會自動返回一個Promise ,當 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
}

使用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 支持很多功能,其中之一是 disposers,它允許您在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拒絕),這將自動結束連接。

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 方庫來進行 promisify。

假設您的數據庫適配器 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 */ });

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

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

在設置 promise 時,您需要使用兩個參數, resolvereject 在成功的情況下,用結果調用resolve ,在失敗的情況下調用reject並返回錯誤。

然后你可以寫:

getUsers().then(callback)

將使用從getUsers返回的承諾結果調用callback ,即result

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

下面的代碼僅適用於 node -v > 8.x

我將這個Promisified MySQL 中間件用於 Node.js

閱讀本文使用 Node.js 8 和 Async/Await 創建 MySQL 數據庫中間件

數據庫.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)
       }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM