Mengganti callback dengan promise di Node.js

94

Saya memiliki modul node sederhana yang terhubung ke database dan memiliki beberapa fungsi untuk menerima data, misalnya fungsi ini:


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

Modul tersebut akan dipanggil dengan cara ini dari modul node yang berbeda:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

Saya ingin menggunakan promise daripada callback untuk mengembalikan data. Sejauh ini saya telah membaca tentang janji bersarang di utas berikut: Menulis Kode Bersih Dengan Janji Bersarang , tetapi saya tidak dapat menemukan solusi apa pun yang cukup sederhana untuk kasus penggunaan ini. Apa cara yang benar untuk kembali resultmenggunakan janji?

Lior Erez
sumber
1
Lihat Adapting Node , jika Anda menggunakan library Q kriskowal.
Bertrand Marron
1
kemungkinan duplikat Bagaimana cara mengonversi API panggilan balik yang ada menjadi promise? Harap buat pertanyaan Anda lebih spesifik, atau saya akan menutupnya
Bergi
@ leo.249: Sudahkah Anda membaca dokumentasi Q? Sudahkah Anda mencoba menerapkannya ke kode Anda - jika ya, harap kirimkan percobaan Anda (meskipun tidak berhasil)? Di mana tepatnya Anda terjebak? Anda tampaknya telah menemukan solusi yang tidak sederhana, silakan posting.
Bergi
3
@ leo.249 Q praktis tidak dipertahankan - pengubahan terakhir dilakukan 3 bulan yang lalu. Hanya cabang v2 yang menarik bagi pengembang Q dan itu bahkan tidak mendekati produksi siap. Ada masalah yang belum terselesaikan tanpa komentar di pelacak masalah mulai Oktober. Saya sangat menyarankan Anda mempertimbangkan perpustakaan janji yang dipelihara dengan baik.
Benjamin Gruenbaum
2
Sangat terkait Cara mengonversi API panggilan balik menjadi janji
Benjamin Gruenbaum

Jawaban:

102

Menggunakan Promisekelas

Saya sarankan untuk melihat dokumen MDN's Promise yang menawarkan titik awal yang baik untuk menggunakan Promises. Atau, saya yakin ada banyak tutorial yang tersedia secara online. :)

Catatan: Browser modern sudah mendukung spesifikasi ECMAScript 6 dari Promises (lihat dokumen MDN yang ditautkan di atas) dan saya berasumsi bahwa Anda ingin menggunakan implementasi asli, tanpa pustaka pihak ketiga.

Adapun contoh sebenarnya ...

Prinsip dasarnya bekerja seperti ini:

  1. API Anda disebut
  2. Anda membuat objek Promise baru, objek ini menggunakan satu fungsi sebagai parameter konstruktor
  3. Fungsi yang Anda sediakan dipanggil oleh implementasi yang mendasari dan fungsi tersebut diberi dua fungsi - resolvedanreject
  4. Setelah Anda melakukan logika, Anda memanggil salah satu dari ini untuk memenuhi Janji atau menolaknya dengan kesalahan

Ini mungkin terlihat banyak, jadi inilah contoh sebenarnya.

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

Menggunakan fitur bahasa async / await (Node.js> = 7.6)

Di Node.js 7.6, compiler JavaScript v8 telah diupgrade dengan dukungan async / await . Anda sekarang dapat mendeklarasikan fungsi sebagai async, yang berarti fungsi tersebut secara otomatis mengembalikan Promiseyang diselesaikan saat fungsi asinkron menyelesaikan eksekusi. Di dalam fungsi ini, Anda dapat menggunakan awaitkata kunci untuk menunggu hingga Promise lainnya terselesaikan.

Berikut ini contohnya:

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
}
Robert Rossmann
sumber
14
Janji adalah bagian dari spesifikasi ECMAScript 2015 dan v8 yang digunakan oleh Node v0.12 menyediakan implementasi dari bagian spesifikasi ini. Jadi ya, mereka bukan bagian dari inti Node - mereka adalah bagian dari bahasa.
Robert Rossmann
1
Perlu diketahui, saya mendapat kesan bahwa untuk menggunakan Promises, Anda perlu menginstal paket npm dan menggunakan require (). Saya menemukan paket promise di npm yang mengimplementasikan gaya barebone / A ++ dan telah menggunakannya, tetapi saya masih baru untuk node itu sendiri (bukan JavaScript).
macguru2000
Ini adalah cara favorit saya untuk menulis promise dan merancang kode asinkron, terutama karena ini merupakan pola yang konsisten, mudah dibaca, dan memungkinkan kode yang sangat terstruktur.
31

Dengan bluebird Anda dapat menggunakan Promise.promisifyAll(dan Promise.promisify) untuk menambahkan metode siap Promise ke objek apa pun.

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

Dan gunakan seperti ini:

getUsersAsync().then(console.log);

atau

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

Menambahkan disposers

Bluebird mendukung banyak fitur, salah satunya adalah disposers, memungkinkan Anda untuk dengan aman membuang koneksi setelah diakhiri dengan bantuan Promise.usingdan Promise.prototype.disposer. Berikut contoh dari aplikasi saya:

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

Kemudian gunakan seperti ini:

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

Ini secara otomatis akan mengakhiri koneksi setelah janji diselesaikan dengan nilai (atau ditolak dengan Error).

Hantu Madara
sumber
3
Jawaban yang sangat bagus, saya akhirnya menggunakan bluebird daripada Q terima kasih, terima kasih!
Lior Erez
2
Ingatlah bahwa menggunakan promise yang Anda setujui untuk digunakan try-catchdi setiap panggilan. Jadi, jika Anda melakukannya cukup sering, dan kompleksitas kode Anda mirip dengan sampel, Anda harus mempertimbangkannya kembali.
Andrey Popov
14

Node.js versi 8.0.0+:

Anda tidak perlu menggunakan bluebird untuk menetapkan metode API node lagi. Karena, dari versi 8+ Anda dapat menggunakan util.promisify asli :

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

Sekarang, tidak perlu menggunakan lib pihak ketiga untuk melakukan janji.

asmmahmud
sumber
3

Dengan asumsi API adaptor database Anda tidak menghasilkan Promisessendiri, Anda dapat melakukan sesuatu seperti:

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

Jika database API mendukung, PromisesAnda dapat melakukan sesuatu seperti: (di sini Anda melihat kekuatan Promises, fluff callback Anda cukup banyak menghilang)

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

Menggunakan .then()untuk mengembalikan janji baru (bersarang).

Telepon dengan:

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

Saya menggunakan API mockup untuk Janji saya, API Anda mungkin berbeda. Jika Anda menunjukkan API Anda, saya bisa menyesuaikannya.

Tenang
sumber
2
Library promise apa yang memiliki Promisekonstruktor dan .promise()metode?
Bergi
Terima kasih. Saya hanya mempraktikkan beberapa node.js dan apa yang saya posting adalah semuanya, contoh yang sangat sederhana untuk mencari cara menggunakan promise. Solusi Anda terlihat bagus tetapi paket npm apa yang harus saya instal agar dapat digunakan promise = new Promise();?
Lior Erez
Meskipun API Anda sekarang mengembalikan Promise, Anda belum menyingkirkan piramida malapetaka, atau membuat contoh tentang cara kerja promise untuk menggantikan callback.
Hantu Madara
@ leo.249 Saya tidak tahu, pustaka Promise apa pun yang sesuai dengan Promises / A + seharusnya bagus. Lihat: promiseaplus.com/@Bergi itu tidak relevan. @SecondRikudo jika API yang Anda tautkan tidak mendukung, Promisesmaka Anda tidak dapat menggunakan callback. Begitu Anda masuk ke wilayah janji, 'piramida' menghilang. Lihat contoh kode kedua tentang cara kerjanya.
Halcyon
@Halcyon Lihat jawaban saya. Bahkan API yang sudah ada yang menggunakan callback bisa "dijanjikan" menjadi API siap Janji, yang menghasilkan kode yang jauh lebih bersih.
Hantu Madara
3

2019:

Gunakan modul asli tersebut const {promisify} = require('util');untuk mengonversi pola callback lama yang biasa menjadi pola promise sehingga Anda bisa mendapatkan keuntungan dari async/awaitkode

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});
});
pery mimon
sumber
2

Saat menyiapkan janji, Anda mengambil dua parameter, resolvedan reject. Jika berhasil, panggil resolvedengan hasil, dalam kasus panggilan gagal rejectdengan kesalahan.

Kemudian Anda bisa menulis:

getUsers().then(callback)

callbackakan dipanggil dengan hasil dari janji yang dikembalikan getUsers, yaituresult

Tom
sumber
2

Menggunakan perpustakaan Q misalnya:

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;   
}
pembuat tas
sumber
1
Akan, lain {d.reject (Error baru (err)); },Perbaiki itu?
Russell
0

Kode di bawah ini hanya berfungsi untuk node -v> 8.x

Saya menggunakan ini middleware MySQL yang Dijanjikan untuk Node.js

baca artikel ini Membuat Database MySQL Middleware dengan Node.js 8 dan 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

Anda harus meningkatkan node -v> 8.x

Anda harus menggunakan fungsi async agar bisa menggunakan await.

contoh:

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