Geri aramaları Node.js'deki vaatlerle değiştirme


94

Bir veritabanına bağlanan ve veri almak için birkaç işlevi olan basit bir düğüm modülüm var, örneğin bu işlev:


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

Modül, farklı bir düğüm modülünden şu şekilde çağrılır:


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

Verileri iade etmek için geri çağırma yerine sözler kullanmak istiyorum. Şimdiye kadar aşağıdaki başlıkta iç içe geçmiş vaatleri okudum: İç İçe Vaatlerle Temiz Kod Yazma , ancak bu kullanım durumu için yeterince basit bir çözüm bulamadım. resultBir söz kullanarak geri dönmenin doğru yolu ne olabilir ?


1
Kriskowal'ın Q kitaplığını kullanıyorsanız Düğümü Uyarlama konusuna bakın .
Bertrand Marron

1
olası kopyası Mevcut bir geri arama API'sini vaatlere nasıl dönüştürebilirim? Lütfen sorunuzu daha spesifik hale getirin, yoksa onu kapatacağım
Bergi

@ leo.249: Q belgelerini okudunuz mu? Kodunuza zaten uygulamayı denediniz mi - evet ise, lütfen denemenizi gönderin (çalışmıyor olsa bile)? Tam olarak nerede sıkıştın? Görünüşe göre basit olmayan bir çözüm bulmuşsunuz, lütfen gönderin.
Bergi

3
@ leo.249 Q pratik olarak bakımsız - son kayıt 3 ay önceydi. Q geliştiricileri için yalnızca v2 şubesi ilgi çekicidir ve bu zaten üretime hazır olmaya yakın bile değildir. Ekim ayından itibaren sorun izleyicide yorum içermeyen ele alınmamış sorunlar var. İyi hazırlanmış bir söz kütüphanesi düşünmenizi şiddetle tavsiye ederim.
Benjamin Gruenbaum

Yanıtlar:


103

PromiseSınıfı kullanma

Promises'i kullanmak için iyi bir başlangıç ​​noktası sunan MDN'nin Promise belgelerine bir göz atmanızı tavsiye ederim . Alternatif olarak, çevrimiçi olarak kullanılabilen birçok eğitim olduğundan eminim. :)

Not: Modern tarayıcılar, Promises'in ECMAScript 6 spesifikasyonunu zaten desteklemektedir (yukarıda bağlantı verilen MDN belgelerine bakın) ve yerel uygulamayı 3. taraf kitaplıkları olmadan kullanmak istediğinizi varsayıyorum.

Gerçek bir örneğe gelince ...

Temel prensip şu şekilde işler:

  1. API'niz çağrılır
  2. Yeni bir Promise nesnesi oluşturursunuz, bu nesne yapıcı parametresi olarak tek bir işlevi alır
  3. Sağladığınız işlev, temel uygulama tarafından çağrılır ve işleve iki işlev verilir - resolvevereject
  4. Mantığınızı yaptıktan sonra, bunlardan birini Sözü yerine getirmek veya bir hata ile reddetmek için çağırırsınız.

Bu çok gibi görünebilir, bu yüzden burada gerçek bir örnek var.

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

Eşzamansız / bekleme dili özelliğini kullanma (Node.js> = 7.6)

Node.js 7.6'da, v8 JavaScript derleyicisi async / await desteği ile yükseltilmiştir . Artık işlevleri var olarak bildirebilirsiniz async; Promisebu, zaman uyumsuz işlev yürütmeyi tamamladığında otomatik olarak çözülen bir a döndürdüğü anlamına gelir . Bu işlevin içinde, awaitbaşka bir Promise çözülene kadar beklemek için anahtar sözcüğü kullanabilirsiniz .

İşte bir örnek:

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
Sözler, ECMAScript 2015 spesifikasyonunun bir parçasıdır ve Node v0.12 tarafından kullanılan v8, spesifikasyonun bu bölümünün uygulanmasını sağlar. Yani evet, Düğüm çekirdeğinin bir parçası değiller - dilin bir parçası.
Robert Rossmann

1
Bilmem güzel, Promises'i kullanmak için bir npm paketi kurmanız ve require () kullanmanız gerektiği izlenimine kapılmıştım. Çıplak kemikler / A ++ stilini uygulayan npm'de söz paketini buldum ve bunu kullandım, ancak yine de düğümün kendisi için yeniyim (JavaScript değil).
macguru2000

Bu, sözler ve mimar eşzamansız kod yazmanın en sevdiğim yoludur, çünkü tutarlı bir modeldir, kolayca okunur ve yüksek düzeyde yapılandırılmış koda izin verir.

31

İle bluebird kullanabilirsiniz Promise.promisifyAll(ve Promise.promisifyherhangi bir nesneye Promise hazır yöntemleri eklemek).

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

Ve bunun gibi kullanın:

getUsersAsync().then(console.log);

veya

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

Öğütücüler ekleme

Bluebird destekleri özellikleri bir sürü bunlardan biri bunun yardımı ile sona erdikten sonra güvenle bağlantı imha sağlayan, disposers olduğunu Promise.usingve Promise.prototype.disposer. İşte uygulamamdan bir örnek:

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

O zaman şu şekilde kullanın:

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

Söz, değer ile çözüldüğünde (veya bir ile reddettiğinde Error) bu, bağlantıyı otomatik olarak sonlandıracaktır .


3
Mükemmel cevap, sayende Q yerine bluebird kullandım, teşekkürler!
Lior Erez

2
try-catchHer aramada kullanmayı kabul ettiğiniz vaatleri kullandığınızı unutmayın . Dolayısıyla, bunu sık sık yaparsanız ve kod karmaşıklığınız örneğe benziyorsa, bunu yeniden düşünmelisiniz.
Andrey Popov

14

Node.js sürüm 8.0.0+:

Artık düğüm API yöntemlerini bildirmek için bluebird kullanmanıza gerek yok. Çünkü, 8+ sürümünden itibaren native util.promisify kullanabilirsiniz :

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

Şimdi, söz vermek için herhangi bir üçüncü taraf kitaplığı kullanmak zorunda değilsiniz.


3

Veritabanı bağdaştırıcısı API'nizin Promiseskendi başına çıktı vermediğini varsayarsak, aşağıdaki gibi bir şey yapabilirsiniz:

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

Veritabanı API'si destekliyorsa Promises, şöyle bir şey yapabilirsiniz: (burada Promises'in gücünü görüyorsunuz, geri arama tüyleriniz hemen hemen yok oluyor)

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

Kullanılması .then()yeni (iç içe) sözünü geri dönmek için.

İle ara:

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

Sözlerim için bir mockup API kullandım, API'niz farklı olabilir. Bana API'nizi gösterirseniz, onu uyarlayabilirim.


2
Kitaplığın bir Promisekurucusu ve .promise()yöntemi nedir?
Bergi

Teşekkür ederim. Ben sadece biraz node.js alıştırması yapıyorum ve yayınladığım tek şey var, vaatlerin nasıl kullanılacağını anlamak için çok basit bir örnek. Çözümünüz iyi görünüyor ama kullanmak için hangi npm paketini yüklemem gerekir promise = new Promise();?
Lior Erez

API'niz artık bir Söz verirken, kıyamet piramidinden kurtulmadınız veya geri aramaların yerini alacak sözlerin nasıl işlediğine dair bir örnek vermediniz.
Madara's Ghost

@ leo.249 Bilmiyorum, Promises / A + ile uyumlu herhangi bir Promise kütüphanesi iyi olmalı. Bakınız: promisesaplus.com/@Bergi , bu alakasız. @SecondRikudo, arayüz oluşturduğunuz API desteklemiyorsa Promisesgeri aramaları kullanmak zorunda kalırsınız. Söz verilen bölgeye girdiğinizde 'piramit' kaybolur. Bunun nasıl çalışacağına ilişkin ikinci kod örneğine bakın.
Halcyon

@Halcyon Cevabımı gör. Geri aramaları kullanan mevcut bir API bile, bir Promise hazır API olarak "vaat edilebilir", bu da tamamen daha temiz bir kod elde edilmesini sağlar.
Madara's Ghost

3

2019:

Bu yerel modülü const {promisify} = require('util');, düz eski geri arama modelini söz modeline dönüştürmek için kullanın, böylece async/awaitkoddan yararlanabilirsiniz

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

Bir söz verirken iki parametre alırsınız resolveve reject. Başarı durumunda resolve, sonuçla rejectbirlikte arayın, başarısızlık durumunda hata ile arayın .

O zaman yazabilirsiniz:

getUsers().then(callback)

callbackgelen sözün sonucu ile çağrılacak getUsers, yaniresult


2

Örneğin Q kütüphanesini kullanarak:

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
Aksi takdirde {d.reject (yeni Hata (err)); }, düzeltilsin mi?
Russell

0

Aşağıdaki kod yalnızca -v> 8.x düğümü için çalışır

Bunu kullanıyorum Promisified MySQL ara yazılımını Node.js için kullanıyorum

bu makaleyi oku Node.js 8 ve Async / Await ile MySQL Veritabanı Ara Yazılımı Oluşturun

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 düğümünü yükseltmelisiniz

await'i kullanabilmek için eşzamansız işlevi kullanmanız gerekir.

misal:

   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)
       }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.