Node.jsでのコールバックのPromiseへの置き換え


94

データベースに接続するシンプルなノードモジュールがあり、この関数など、データを受信するためのいくつかの関数があります。


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

このモジュールは、別のノードモジュールから次のように呼び出されます。


app.js:

import dbCon from './dbConnection.js';

dbCon.getUsers(console.log);

データを返すために、コールバックの代わりにプロミスを使用したいと思います。これまでのところ、次のスレッドでネストされたプロミスについて読みました:ネストされたプロミスを使用したクリーンなコードの記述。resultプロミスを使用して戻る正しい方法は何ですか?


1
kriskowalのQライブラリを使用している場合は、「ノードの適応」を参照してください。
Bertrand Marron、2015

1
既存のコールバックAPIをpromiseに変換するにはどうすればよいですか?あなたの質問をより具体的にしてください、または私はそれを
締めくくり

@ leo.249:Qドキュメントを読みましたか?あなたはすでにそれをあなたのコードに適用しようとしたことがあります-もしそうなら、あなたの試みを投稿してください(たとえうまくいかなくても)?どこに行き詰まっているのですか?簡単ではない解決策を見つけたようです。投稿してください。
Bergi、2015

3
@ leo.249 Qは実質的にメンテナンスされていません-最後のコミットは3か月前でした。Q開発者にとって興味深いのはv2ブランチだけであり、いずれにしても本番稼働の準備が整っているとは言えません。10月から、問題トラッカーにコメントのない未解決の問題があります。よく整備されたpromiseライブラリを検討することを強くお勧めします。
Benjamin Gruenbaum、2015

2
スーパー関連コールバックAPIを
promise

回答:


102

Promiseクラスの使用

MDNのPromiseドキュメントご覧になることをお勧めします。これは、Promiseを使用するための良い出発点となります。または、オンラインで利用できるチュートリアルがたくさんあると思います。

注:最新のブラウザはすでにPromiseのECMAScript 6仕様をサポートしており(上記のMDNドキュメントを参照)、サードパーティのライブラリなしでネイティブ実装を使用することを想定しています。

実際の例は...

基本的な原理は次のように機能します。

  1. APIが呼び出されます
  2. 新しいPromiseオブジェクトを作成します。このオブジェクトは、コンストラクターパラメーターとして単一の関数を取ります
  3. 提供された関数は、基礎となる実装によって呼び出され、関数には2つの関数が与えられます- resolveおよびreject
  4. ロジックを実行したら、これらのいずれかを呼び出して、Promiseを完全に満たすか、エラーで拒否します

これは多くのように思えるかもしれませんので、ここに実際の例があります。

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言語機能の使用(Node.js> = 7.6)

Node.js 7.6では、v8 JavaScriptコンパイラがasync / awaitサポートでアップグレードされました。関数をis として宣言できるようasyncになりました。つまりPromise、async関数が実行を完了すると解決されるa を自動的に返します。この関数内では、awaitキーワードを使用して、別のPromiseが解決されるまで待機できます。

次に例を示します。

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
PromiseはECMAScript 2015仕様の一部であり、Node v0.12で使用されるv8は仕様のこの部分の実装を提供します。つまり、これらはNodeコアの一部ではなく、言語の一部です。
Robert Rossmann、2015

1
知っておくと、Promiseを使用するには、npmパッケージをインストールしてrequire()を使用する必要があるという印象を受けました。裸の骨/ A ++スタイルを実装するnpmのpromiseパッケージを見つけて使用しましたが、それでもノード自体(JavaScriptではない)は初めてです。
macguru2000 2015

これは、主に一貫したパターンであり、読みやすく、高度に構造化されたコードを可能にするため、Promiseとアーキテクトの非同期コードを書く私のお気に入りの方法です。

31

青い鳥を使用できPromise.promisifyAll(およびPromise.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.
});

ディスポーザーの追加

ブルーバードは、それがの助けを借りて、終わった後に、安全な接続を処分することができ、そのうちの一つは、ディスポーザーで、機能の多くをサポート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')
        });
};

これにより、promiseが値で解決される(またはで拒否されるError)と、接続は自動的に終了します。


3
すばらしい回答でした。おかげで、Qの代わりにbluebirdを使用することになりました。ありがとうございます。
Lior Erez、2015

2
try-catch各コールで使用することに同意したプロミスを使用することに注意してください。したがって、頻繁に実行し、コードの複雑さがサンプルに似ている場合は、これを再検討する必要があります。
Andrey Popov、2015

14

Node.jsバージョン8.0.0+:

ノードAPIメソッドを約束するためにbluebirdを使用する必要はもうありません。バージョン8以降では、ネイティブのutil.promisifyを使用できるためです。

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を使う必要はありません。


3

データベースアダプター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次のようなことができます(Promiseの力を見ると、コールバックの綿毛がほとんど消えます)。

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

.then()新しい(ネストされた)プロミスを返すために使用します。

次の電話番号:

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

PromiseのモックアップAPIを使用しました。APIが異なる場合があります。あなたのAPIを見せてくれれば、私はそれを調整することができます。


2
どのプロミスライブラリにPromiseコンストラクタと.promise()メソッドがありますか?
Bergi、2015

ありがとうございました。私はいくつかのnode.jsを練習しているだけで、投稿したのはそれだけです。promiseの使い方を理解するための非常に単純な例です。あなたのソリューションは良さそうpromise = new Promise();ですが、使用するためにどのnpmパッケージをインストールする必要がありますか?
Lior Erez、2015

APIがPromiseを返すようになりましたが、破滅のピラミッドを解消したり、コールバックを置き換えるためにPromiseがどのように機能するかを示す例はありません。
マダラのゴースト

@ leo.249わかりませんが、Promises / A +に準拠しているPromiseライブラリはどれも良いはずです。参照:promisesaplus.com/@Bergiは無関係です。@SecondRikudoインターフェースをとるAPIがサポートしていない場合、Promisesコールバックの使用に行き詰まります。約束の領土に入ると、「ピラミッド」が消えます。それがどのように機能するかについての2番目のコード例をご覧ください。
Halcyon

@ハルシオン私の答えを参照してください。コールバックを使用する既存のAPIでも、Promise対応のAPIに "約束"することができるため、コードがよりクリーンになります。
マダラのゴースト

3

2019:

そのネイティブモジュールconst {promisify} = require('util');を使用して、プレーンな古いコールバックパターンをpromiseパターンに変換し、async/awaitコードからメリットを得ることができます

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

promiseを設定するときは、2つのパラメーターとresolveを使用しrejectます。成功resolveした場合は結果を呼び出し、失敗した場合はエラーを呼び出しrejectます。

それからあなたは書くことができます:

getUsers().then(callback)

callbackから返されたpromiseの結果で呼び出されますgetUsers。つまり、result


2

たとえば、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;   
}

1
そうでなければ、{d.reject(new Error(err)); }、それを修正しますか?
ラッセル

0

以下のコードはノード-v> 8.xでのみ機能します

Node.jsにこの約束されたMySQLミドルウェアを使用しています

この記事を読むNode.js 8とAsync / Awaitを使用してMySQLデータベースミドルウェアを作成する

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をアップグレードする必要があります

awaitを使用するには、非同期関数を使用する必要があります。

例:

   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)
       }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.