Замена обратных вызовов обещаниями в узле.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);

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

7 83

7 ответов:

С помощью Promise класс

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

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

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

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

  1. ваш API называется
  2. вы создаете новый объект Promise, этот объект принимает одну функцию в качестве параметра конструктора
  3. ваша предоставленная функция вызывается базовой реализацией, и функция получает две функции -resolve и reject
  4. как только вы сделаете свою логику, вы назовете один из них либо заполните обещание или отклоните его с ошибкой

это может показаться много, так что вот реальный пример.

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 (узел.js >=7.6)

В Узел.js 7.6, компилятор JavaScript v8 был обновлен с асинхронный/ждут поддержки. Теперь вы можете объявить функции как 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.promisifyAllPromise.promisify), чтобы добавить обещание готовые методы любого объекта.

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.using и Promise.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).

узел.js версия 8.0.0+:

вы не должны использовать птица чтобы больше обещать методы API узла. Потому что, начиная с версии 8+ вы можете использовать родной util.обещай:

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

теперь, не нужно использовать любую стороннюю lib, чтобы сделать 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 вы могли бы сделать что-то вроде: (здесь вы видите силу обещаний, ваш обратный вызов пух в значительной степени исчезает)

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

используя .then() чтобы вернуть новое (вложенное) обещание.

звонок с:

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

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

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

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

getUsers().then(callback)

callback будет вызван с результатом обещания, возвращенного от getUsers, т. е. 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;   
}

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

Я использую этот Promisified MySQL middleware для узла.js

прочитать эту статью создайте промежуточное программное обеспечение базы данных MySQL с узлом.js 8 и Async / Await