webentwicklung-frage-antwort-db.com.de

Callbacks durch Versprechen in Node.js ersetzen

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 aus 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: Clean Code mit verschachtelten Versprechen schreiben , aber ich konnte es nicht finden Jede Lösung, die für diesen Anwendungsfall einfach genug ist ... Was wäre der richtige Weg, um result mit einem Versprechen zurückzugeben?

85
Lior Erez

Verwendung der Promise-Klasse

Ich empfehle einen Blick auf MDN's Promise docs , die einen guten Ausgangspunkt für die Verwendung von Promises bieten. Alternativ bin ich sicher, dass es viele Tutorials online gibt. :)

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 Drittanbieter-Bibliotheken verwenden möchten.

Wie für ein aktuelles Beispiel ...

Das Grundprinzip funktioniert so:

  1. Ihre API wird aufgerufen
  2. Wenn Sie ein neues Promise-Objekt erstellen, nimmt dieses Objekt eine einzige Funktion als Konstruktorparameter an
  3. Ihre bereitgestellte Funktion wird von der zugrunde liegenden Implementierung aufgerufen und der Funktion stehen zwei Funktionen zur Verfügung: resolve und reject
  4. Sobald Sie Ihre Logik ausgeführt haben, rufen Sie eine davon an, um die Versprechen entweder zu erfüllen oder sie mit einem Fehler abzulehnen

Dies mag viel erscheinen, hier also 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 async/await support aktualisiert. Sie können jetzt Funktionen als async deklarieren, dh sie geben automatisch eine Promise zurück, die aufgelöst wird, wenn die async-Funktion die Ausführung abschließt. Innerhalb dieser Funktion können Sie mit dem Schlüsselwort await 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
}
90
Robert Rossmann

Mit bluebird können Sie mit Promise.promisifyAll (und Promise.promisify ) jedem Objekt Promise ready-Methoden hinzufü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 wie folgt verwenden:

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

Disposer hinzufügen

Bluebird unterstützt eine Vielzahl von Funktionen, darunter auch Disposer. Mit Hilfe von Promise.using und Promise.prototype.disposer können Sie eine Verbindung nach dem Beenden sicher trennen. 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 einer Error abgelehnt wird).

31
Madara Uchiha

Node.js Version 8.0.0+:

Sie müssen nicht mehr bluebird verwenden, um die API-Methoden des Knotens zu promoten. 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-Lib verwenden, um das Versprechen zu machen. 

9
asmmahmud

Wenn Sie davon ausgehen, dass Ihre Datenbankadapter-API Promises nicht selbst ausgibt, 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 Promises unterstützt, könnten Sie Folgendes tun: (Hier sehen Sie die Möglichkeiten von Promises, Ihre Callback-Fluff verschwindet 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.

Anrufen mit:

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

Ich habe eine Mockup-API für meine Versprechen verwendet. Ihre API könnte anders sein. Wenn Sie mir Ihre API zeigen, kann ich sie anpassen.

3
Halcyon

Beim Einrichten eines Versprechens nehmen Sie zwei Parameter, resolve und reject. Im Erfolgsfall rufen Sie resolve mit dem Ergebnis auf, im Fehlerfall rufen Sie reject mit dem Fehler auf.

Dann kannst du schreiben:

getUsers().then(callback)

callback wird mit dem Ergebnis der Zusage aufgerufen, die von getUsers zurückgegeben wird, d. h. result

2
Tom

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

2019:

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

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

2
pery mimon

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

Ich benutze 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

datenbank.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 async-Funktion verwenden, um mit wait warten 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)
       }
0
hoogw