Замена обратных вызовов с помощью promises в Node.js

У меня есть простой модуль node, который подключается к базе данных и имеет несколько функций для приема данных, например, эту функцию:


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

Модуль будет вызван таким образом из другого модуля node:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

Я хотел бы использовать promises вместо обратных вызовов, чтобы вернуть данные. До сих пор я читал о вложенных promises в следующем потоке: Написание чистого кода с вложенным Promises, но я не мог найти какое-либо решение, которое просто достаточно для этого варианта использования. Каким будет правильный способ вернуть result с помощью обещания?

Ответ 1

Использование класса Promise

Я рекомендую взглянуть на MDN Promise docs, которые предлагают хорошую отправную точку для использования Promises. Кроме того, я уверен, что в Интернете доступно много учебников.:)

Примечание. Современные браузеры уже поддерживают спецификацию ECMAScript 6 Promises (см. документы MDN, приведенные выше), и я предполагаю, что вы хотите использовать собственную реализацию без сторонних библиотек. p >

Что касается фактического примера...

Основной принцип работает следующим образом:

  • Ваш 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 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
  })

Использование функции языка асинхронного/ожидающего (Node.js >= 7.6)

В Node.js 7.6 компилятор v8 JavaScript был обновлен с помощью поддержки async/await. Теперь вы можете объявлять функции как async, что означает, что они автоматически возвращают Promise, который разрешается, когда функция async завершает выполнение. Внутри этой функции вы можете использовать ключевое слово 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
}

Ответ 2

С bluebird вы можете использовать Promise.promisifyAllPromise.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 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).

Ответ 3

Node.js версия 8.0. 0+:

Вы не должны использовать Bluebird больше promisify методы узла 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')
        });
};

Теперь не нужно использовать какую-либо стороннюю библиотеку для выполнения обещания.

Ответ 4

Предполагая, что 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 */ });

Я использовал mockup API для своего Promises, ваш API мог бы быть другим. Если вы покажете мне свой API, я смогу его адаптировать.

Ответ 5

При настройке обещания вы берете два параметра: resolve и reject. В случае успеха вызовите resolve с результатом, в случае вызова отказа reject с ошибкой.

Затем вы можете написать:

getUsers().then(callback)

callback будет вызван с результатом обещания, возвращенного из getUsers, то есть result

Ответ 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;   
}

Ответ 7

Код ниже работает только для узла -v> 8.x

Я использую это промежуточное ПО Promisified MySQL для Node.js

Прочтите эту статью. Создайте промежуточное ПО базы данных MySQL с Node.js 8 и 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> 8.x

Вы должны использовать асинхронную функцию, чтобы иметь возможность использовать 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)
       }

Ответ 8

2019:

Используйте этот собственный модуль const {promisify} = require('util'); преобразовать простой старый шаблон обратного вызова в шаблон обещания, чтобы вы могли получить пользу от кода 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});
});