web-dev-qa-db-ja.com

Node.js:PromisesとCallbacksを使用する場合

更新中の古いNode.jsコードがいくつかあります。その過程で、古いコードで動作する新しいモジュールを設計しています。私がこれを最初に書いたときとは対照的に、私は今、コールバックではなくES6プロミスの使用に依存していることを発見しています。だから今、私は約束を返すいくつかの関数とコールバックを取るいくつかのこのミックスを持っています-これは退屈です。最終的には、Promiseを使用するようにリファクタリングする必要があると思います。しかし、それが行われる前に...

約束が優先され、コールバックが優先される状況は何ですか?

コールバックが約束よりもうまく処理できる状況と、その逆の状況はありますか?

これまで見てきたことを基にすると、promiseの代わりにコールバックを使用する理由が本当にわかりません。本当?

16
Sean Lynch

まず、コールバックと非同期操作の約束を組み合わせたコードを記述することはほとんどありません。 promiseに移行する場合、またはいくつかのpromiseを導入する場合は、おそらくコードの同じセクション内のコールバックをpromiseにリファクタリングする必要があります。適切なタイプの操作については、単純なコールバックよりも多くのpromiseの利点があるため、すでにコードの領域で作業しているときに変換するのに十分な価値があります。

約束は素晴らしい:

  • 同期操作の監視
  • 1回だけ通知する必要がある(通常は完了またはエラー)
  • 非同期操作の順序付けや分岐、または飛行中の複数の操作の同時管理など、複数の非同期操作の調整または管理
  • ネストされた、または深くネストされた非同期操作からのエラーの伝播
  • Async/awaitを使用するためのコードの準備(または、トランスパイラーでコードを使用する)
  • pendingfulfilledrejectedの3つの状態のみがあり、pending => fulfilledまたはpending => rejectedからの状態遷移が変更できないPromiseモデルに適合する操作一方向の移行)。
  • 非同期操作の動的なリンクまたはチェーン(これら2つの非同期操作を実行し、結果を調べ、中間結果に基づいて他の非同期操作を決定するなど)
  • 非同期操作と同期操作の混在を管理する
  • 非同期完了コールバックで発生するすべての例外を自動的にキャッチして上方に伝播します(プレーンコールバックでは、これらの例外は静かに隠されることがあります)。

プレーンコールバックは、promiseができないことには適しています:

  • 同期通知(Array.prototype.map()のコールバックなど)
  • 複数回発生する可能性がある(したがって、コールバックを複数回呼び出す必要がある)通知。プロミスはワンショットデバイスであり、繰り返し通知に使用することはできません。
  • 保留中、履行済み、拒否済みの一方向状態モデルにマッピングできない状況。

そして、EventEmitterもミックスに追加します。

EventEmitterは以下に最適です:

  • パブリッシュ/サブスクライブタイプの通知
  • 特にイベントが複数回発生する可能性がある場合(ストリームなど)、イベントモデルとのインターフェイス
  • EventEmitterよりもAPIを使用せずに、サードパーティのコードが参加したり監視したりする場合の疎結合。設計するAPIはありません。 eventEmitterをパブリックにして、いくつかのイベントとそれに付随するデータを定義するだけです。

プレーンコールバックコードをPromisesに変換する際の注意事項

コールバックが、最後の引数として渡され、このcallback(err, result)のように呼び出されるコールバックを使用して、ノードの呼び出し規約に適合する場合、ノードでutil.promisify()を含むプロミスで親関数をいくらか自動的にラップします。 jsまたは Bluebird promise library を使用する場合、 Promise.promisify() を使用します。

Bluebirdを使用すると、次のような(node.js呼び出し規約で非同期コールバックを使用する)モジュール全体を一度に約束することもできます。

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.writeFileAsync("file.txt", data).then(() => {
    // done here
}).catch(err => {
    // error here
});

node.jsバージョン8 +

Node.js非同期呼び出し規約を使用する非同期関数を、promiseを返す関数に変換するutil.promisify()があります。

doc: の例

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);

// usage of promisified function
stat('.').then((stats) => {
  // Do something with `stats`
}).catch((error) => {
  // Handle the error.
});
33
jfriend00

どちらも同じ問題を解決し、非同期関数の結果を処理するために存在します。

コールバックはより冗長になる傾向があり、複数の非同期リクエストを同時に調整すると、 callback hell につながる可能性があります。関数。エラー処理およびトレースは、コールスタックのさらに1つのエラーにすべて戻る多くのErrorオブジェクトが存在する可能性があるため、それほど簡単ではなく、混乱を招く傾向があります。また、エラーは、コールバックチェーンで匿名関数が使用された場合に元のエラーがスローされた場所を判断するときに頭を傷つける可能性がある、元の呼び出し元に戻す必要があります。コールバックの利点の1つは、単純な古い関数であり、非同期操作がどのように機能するかを知ること以上の追加の理解を必要としないことです。

約束はより少ないコードを必要とするためより一般的であり、同期関数のように書かれ、単一のエラーチャネルを持ち、スローされたエラーを処理でき、 util.promisify() Node.jsの最新バージョンは、Error-First Callbacksをpromiseに変換できます。 Node.jsへの移行 であるasync/awaitもあり、Promiseとのインターフェースもあります。

これは完全に意見に基づいているため、実際に最も快適なものについてですが、Promiseとasync/awaitはコールバックの進化であり、非同期開発エクスペリエンスを向上させます。これは、決して徹底的な比較ではなく、コールバックとプロミスの両方の高レベルな見方です。

7
peteb

私はこのようなものをどこから手に入れたか覚えていませんが、約束をよりよく理解するのに役立つかもしれません。

約束はコールバックではありません。 promiseは、非同期操作の将来の結果を表します。もちろん、あなたのやり方でそれらを書いても、ほとんど利益はありません。ただし、使用する方法で記述すれば、同期コードに似た方法で非同期コードを記述でき、より簡単に追跡できます。[〜#〜] advantages [〜#〜] 1.コールバックの読みやすさ2.エラーをキャッチしやすい。 3.同時コールバック

1。コールバックの読みやすさ Promiseは、JavaScriptで順次非同期操作を表現する、より簡潔で明確な方法を提供します。これらは、コールバックと同じ効果を達成するための実質的に異なる構文です。利点は、読みやすさが向上することです。このようなもの

aAsync()   
.then(bAsync)  
 .then(cAsync)   
.done(finish); 

これらの個々の関数をコールバックとして渡すのと同等の方法よりもはるかに読みやすい

aAsync(function(){
    return bAsync(function(){
        return cAsync(function(){
            finish()         
        })     
    }) 
}); 

2。エラーをキャッチするのは簡単です。確かに、それほどコードは少なくありませんが、はるかに読みやすくなっています。しかし、これで終わりではありません。真の利点を見つけましょう:いずれかのステップでエラーをチェックしたい場合はどうでしょうか?コールバックでそれを行うのは地獄になりますが、約束では簡単です:

api()
.then(function(result) {   
    return api2(); 
})
.then(function(result2){     
    return api3(); 
})
.then(function(result3){      
    // do work 
})
.catch(function(error) {    
    //handle any error that may occur before this point 
}); 
/* Pretty much the same as a try { ... } catch block. 
Even better: */
api()
.then(function(result){
    return api2(); })
.then(function(result2){
    return api3(); })
.then(function(result3){
    // do work 
})
.catch(function(error) {
    //handle any error that may occur before this point 
})
.then(function() {
    //do something whether there was an error or not      
    //like hiding an spinner if you were performing an AJAX request. 
});

。同時コールバックそしてさらに良い: api、api2、api3へのこれらの3つの呼び出しが同時に実行できた場合(たとえば、AJAX呼び出し)でしたが、約束がなければ、カウンターを作成する必要がありますが、約束では、ES6表記を使用すると、別の簡単できれいなものになります。

Promise.all([api(), api2(), api3()])
.then(function(result) {
    //do work. result is an array containing the values of the three fulfilled promises. 
})
.catch(function(error) {
    //handle the error. At least one of the promises rejected. 
});

Promisesが今新しい光で見られることを願っています.

1
Krishnadas PC