Rückrufe durch Versprechen in Node.js ersetzen


94

Ich habe ein einfaches Knotenmodul, das eine Verbindung zu einer Datenbank herstellt und mehrere Funktionen zum Empfangen von Daten hat, zum Beispiel diese Funktion:


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

Das Modul würde auf diese Weise von einem anderen Knotenmodul aufgerufen:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

Ich möchte Versprechen anstelle von Rückrufen verwenden, um die Daten zurückzugeben. Bisher habe ich im folgenden Thread über verschachtelte Versprechen gelesen: Schreiben von sauberem Code mit verschachtelten Versprechen , aber ich konnte keine Lösung finden, die für diesen Anwendungsfall einfach genug ist. Was wäre der richtige Weg, um resultmit einem Versprechen zurückzukehren?


1
Siehe Anpassen des Knotens , wenn Sie die Q-Bibliothek von kriskowal verwenden.
Bertrand Marron

1
Mögliches Duplikat von Wie konvertiere ich eine vorhandene Rückruf-API in Versprechen? Bitte machen Sie Ihre Frage genauer, oder ich werde sie schließen
Bergi

@ leo.249: Hast du die Q-Dokumentation gelesen? Haben Sie bereits versucht, es auf Ihren Code anzuwenden - wenn ja, veröffentlichen Sie bitte Ihren Versuch (auch wenn er nicht funktioniert)? Wo genau steckst du fest? Sie scheinen eine nicht einfache Lösung gefunden zu haben, bitte posten Sie sie.
Bergi

3
@ leo.249 Q ist praktisch nicht gewartet - das letzte Commit war vor 3 Monaten. Nur der v2-Zweig ist für Q-Entwickler interessant und das ist sowieso nicht annähernd produktionsbereit. Es gibt nicht adressierte Probleme ohne Kommentare im Issue-Tracker ab Oktober. Ich empfehle Ihnen dringend, eine gut gepflegte Versprechensbibliothek in Betracht zu ziehen.
Benjamin Gruenbaum

Antworten:


102

Verwendung der Promise Klasse

Ich empfehle, einen Blick auf die Promise-Dokumente von MDN zu werfen die einen guten Ausgangspunkt für die Verwendung von Promises bieten. Alternativ bin ich sicher, dass online viele Tutorials verfügbar sind. :)

Hinweis: Moderne Browser unterstützen bereits die ECMAScript 6-Spezifikation von Promises (siehe die oben verlinkten MDN-Dokumente), und ich gehe davon aus, dass Sie die native Implementierung ohne Bibliotheken von Drittanbietern verwenden möchten.

Wie für ein aktuelles Beispiel ...

Das Grundprinzip funktioniert folgendermaßen:

  1. Ihre API wird aufgerufen
  2. Wenn Sie ein neues Promise-Objekt erstellen, übernimmt dieses Objekt eine einzelne Funktion als Konstruktorparameter
  3. Ihre bereitgestellte Funktion wird von der zugrunde liegenden Implementierung aufgerufen, und die Funktion erhält zwei Funktionen - resolveundreject
  4. Sobald Sie Ihre Logik ausgeführt haben, rufen Sie eine dieser Methoden auf, um das Versprechen entweder zu erfüllen oder es mit einem Fehler abzulehnen

Dies scheint viel zu sein, daher hier ein aktuelles Beispiel.

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

Verwenden der Sprachfunktion async / await (Node.js> = 7.6)

In Node.js 7.6 wurde der v8-JavaScript-Compiler mit asynchroner / warten-Unterstützung aktualisiert . Sie können jetzt Funktionen als solche deklarieren. Dies asyncbedeutet, dass sie automatisch eine zurückgeben, Promisedie aufgelöst wird, wenn die asynchrone Funktion die Ausführung abgeschlossen hat. Innerhalb dieser Funktion können Sie die verwendenawait Schlüsselwort warten, bis ein anderes Versprechen aufgelöst wird.

Hier ist ein Beispiel:

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
}

14
Versprechen sind Teil der ECMAScript 2015-Spezifikation und Version 8, die von Node Version 0.12 verwendet wird, bietet die Implementierung dieses Teils der Spezifikation. Also ja, sie sind nicht Teil des Knotenkerns - sie sind Teil der Sprache.
Robert Rossmann

1
Gut zu wissen, ich hatte den Eindruck, dass Sie zur Verwendung von Promises ein npm-Paket installieren und require () verwenden müssen. Ich habe das Versprechen-Paket auf npm gefunden, das Bare-Bones / A ++ - Stil implementiert, und habe es verwendet, bin aber noch neu im Knoten selbst (nicht JavaScript).
Macguru2000

Dies ist meine Lieblingsmethode zum Schreiben von Versprechungen und asynchronem Code für Architekten, hauptsächlich weil es sich um ein konsistentes Muster handelt, das leicht zu lesen ist und stark strukturierten Code ermöglicht.

31

Mit Bluebird können Sie Promise.promisifyAll(und Promise.promisify) verwenden, um jedem Objekt Promise-fähige Methoden hinzuzufügen.

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

Und verwenden Sie wie folgt:

getUsersAsync().then(console.log);

oder

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

Entsorger hinzufügen

Bluebird unterstützt viele Funktionen, eine davon sind Entsorger. Mit Hilfe von Promise.usingund können Sie eine Verbindung sicher entsorgen, nachdem sie beendet wurde Promise.prototype.disposer. Hier ist ein Beispiel aus meiner App:

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

Dann benutze es so:

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

Dadurch wird die Verbindung automatisch beendet, sobald das Versprechen mit dem Wert aufgelöst wird (oder mit einem abgelehnt wird Error).


3
Ausgezeichnete Antwort, ich habe Bluebird anstelle von Q verwendet, danke, danke!
Lior Erez

2
Denken Sie daran, dass Sie Versprechen verwenden, die Sie try-catchbei jedem Anruf verwenden. Wenn Sie dies also häufig tun und Ihre Codekomplexität dem Beispiel ähnelt, sollten Sie dies überdenken.
Andrey Popov

14

Node.js Version 8.0.0+:

Sie müssen Bluebird nicht mehr verwenden , um die Knoten-API-Methoden zu versprechen. Denn ab Version 8 können Sie native util.promisify verwenden :

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

Jetzt müssen Sie keine Drittanbieter-Bibliothek mehr verwenden, um das Versprechen zu machen.


3

Angenommen, Ihre Datenbankadapter-API gibt sich nicht Promisesselbst aus, können Sie Folgendes tun:

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

Wenn die Datenbank-API dies unterstützt, können PromisesSie Folgendes tun: (Hier sehen Sie die Kraft von Promises, Ihr Rückruf-Flaum verschwindet so ziemlich)

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

Verwenden von .then() , um ein neues (verschachteltes) Versprechen zurückzugeben.

Rufen Sie an mit:

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

Ich habe für meine Versprechen eine Modell-API verwendet. Ihre API ist möglicherweise anders. Wenn Sie mir Ihre API zeigen, kann ich sie anpassen.


2
Welche Versprechensbibliothek hat einen PromiseKonstruktor und eine .promise()Methode?
Bergi

Danke dir. Ich übe nur einige node.js und was ich gepostet habe, war alles, was dazu gehört, ein sehr einfaches Beispiel, um herauszufinden, wie man Versprechen verwendet. Ihre Lösung sieht gut aus, aber welches npm-Paket müsste ich installieren, um es verwenden zu können promise = new Promise();?
Lior Erez

Während Ihre API jetzt ein Versprechen zurückgibt, haben Sie die Pyramide des Untergangs nicht beseitigt oder ein Beispiel dafür erstellt, wie Versprechen funktionieren, um Rückrufe zu ersetzen.
Madaras Geist

@ leo.249 Ich weiß nicht, dass jede Promise-Bibliothek, die mit Promises / A + kompatibel ist, gut sein sollte. Siehe: versprechenaplus.com/@Bergi es ist irrelevant. @SecondRikudo Wenn die API, mit der Sie eine Schnittstelle herstellen, nicht unterstützt Promiseswird, können Sie keine Rückrufe mehr verwenden. Sobald Sie in das Gebiet der Verheißung geraten, verschwindet die 'Pyramide'. Sehen Sie sich das zweite Codebeispiel an, wie das funktionieren würde.
Halcyon

@Halcyon Siehe meine Antwort. Sogar eine vorhandene API, die Rückrufe verwendet, kann in eine Promise-fähige API "versprochen" werden, was insgesamt zu einem viel saubereren Code führt.
Madaras Geist

3

2019:

Verwenden Sie dieses native Modul const {promisify} = require('util');, um ein einfaches altes Rückrufmuster in ein Versprechungsmuster umzuwandeln, damit Sie vom async/awaitCode profitieren können

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

Beim Einrichten eines Versprechens nehmen Sie zwei Parameter resolveund reject. Im Erfolgsfall resolvemit dem Ergebnis aufrufen, rejectim Fehlerfall mit dem Fehler aufrufen .

Dann können Sie schreiben:

getUsers().then(callback)

callbackwird mit dem Ergebnis des versprochenen Versprechens aufgerufen getUsers, dhresult


2

Verwenden der Q-Bibliothek zum Beispiel:

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

1
Würde sonst {d.reject (neuer Fehler (err)); },repariere das?
Russell

0

Der folgende Code funktioniert nur für den Knoten -v> 8.x.

Ich verwende diese Promisified MySQL-Middleware für Node.js.

Lesen Sie diesen Artikel Erstellen Sie eine MySQL-Datenbank-Middleware mit Node.js 8 und 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

Sie müssen den Knoten -v> 8.x aktualisieren

Sie müssen die asynchrone Funktion verwenden, um wait verwenden zu können.

Beispiel:

   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)
       }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.