web-dev-qa-db-ja.com

約束は単にコールバックではありませんか?

私は数年前からJavaScriptを開発してきましたが、約束に関する混乱をまったく理解していません。

私がしているのは変更だけのようです。

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

私はとにかく、 async のようなライブラリを使うことができました。

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

これは、より多くのコードで読みにくいものです。私はここで何も得なかった、それは突然魔法のように「平ら」でもない。物事を約束に変換する必要があることは言うまでもありません。

それで、ここでの約束についての大騒ぎは何ですか?

379

約束はコールバックではありません。約束は、非同期操作の将来の結果を表します。もちろん、自分のやり方でそれらを書いても、ほとんど利益はありません。しかし、それらが使用されることを意図している方法でそれらを書くならば、あなたは同期コードに似ていて従うことがはるかに簡単である方法で非同期コードを書くことができます:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

確かに、はるかに少ないコードではなく、はるかに読みやすくなっています。

しかし、これで終わりではありません。本当の利点を発見しましょう。もしあなたがステップのどれかでエラーをチェックしたいとしたら?それはコールバックでそれをすることは地獄でしょうが、約束で、ケーキの切れ端です:

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

try { ... } catchブロックとほぼ同じです。

さらにもっと良い:

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

さらに良いことに、これらの3つのapiapi2api3の呼び出しが同時に実行される可能性がある場合(たとえば、それらがAJAXの呼び出しである場合)、3つ待つ必要がありますか?約束がなければ、ある種のカウンターを作成する必要があります。 ES6表記を使用することをお約束しますが、もう1つの簡単できちんとした方法があります。

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

Promiseが今、新しい光の中でご覧になれば幸いです。

569
Oscar Paz

はい、Promiseは非同期コールバックです。コールバックではできないことは何もできず、プレーンコールバックと同じように非同期でも同じ問題に直面します。

しかし、Promiseは単なるコールバックよりもmoreです。これらは非常に強力な抽象概念であり、エラーが発生しにくい定型句を使用して、よりクリーンで優れた機能コードを実現できます。

だから、主なアイデアは何ですか?

プロミスは、単一の(非同期)計算の結果を表すオブジェクトです。それらは resolveにその結果 に一度だけ。これが意味するものがいくつかあります。

約束は、オブザーバパターンを実装します。

  • タスクが完了する前に値を使用するコールバックを知る必要はありません。
  • 関数への引数としてコールバックを期待する代わりに、Promiseオブジェクトを簡単にreturnにすることができます。
  • 約束は値を保存します、そしてあなたはいつでもコールバックを追加することができますtransparently。結果が利用可能になったときに呼び出されます。 「透明度」は、約束がありそれにコールバックを追加しても、結果がまだ到着しているかどうかにかかわらず、コードに影響を与えないことを意味します。
  • 複数のコールバックを簡単に追加できます

約束は連鎖可能ですmonadic必要な場合は ):

  • Promiseが表す値を変換する必要がある場合は、mappromiseに対する変換関数を使用して、変換結果を表す新しいpromiseを返します。どういうわけかそれを使用するための値を同期的に取得することはできませんが、約束の文脈で簡単にlift変換を行うことができます。定型コールバックはありません。
  • 2つの非同期タスクをチェーンしたい場合は、.then()メソッドを使用できます。最初の結果とともにコールバックが呼び出されるようにし、コールバックが返すという約束の結果に対する約束を返します。

複雑に聞こえますか?コード例のための時間.

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)

平坦化は不思議なことではありませんが、簡単にできます。非常にネストされた例では、(近い)等価物は次のようになります。

api1().then(api2).then(api3).then(/* do-work-callback */);

これらのメソッドのコードを見て理解するのに役立ちます、 ここに数行で最も基本的な約束のlibがあります

約束についての大騒ぎは何ですか?

Promise抽象化により、関数の合成性が大幅に向上します。例えば、チェーニングのためのthenの次に、all関数は複数の並列待機の約束の結果を組み合わせた約束を作成します。

大事なことを言い忘れていましたが約束は統合されたエラー処理と来ます。計算の結果は、約束が充足であるか、またはrejectedであるかのいずれかです。理由です。すべての合成関数はこれを自動的に処理し、プロミスチェーンでエラーを伝播します。そのため、プレーンコールバックの実装とは異なり、明示的にいたるところでそれを気にする必要はありません。最後に、発生したすべての例外に対して専用のエラーコールバックを追加できます。

物事を約束に変換する必要があることは言うまでもありません。

既存のコールバックAPIをプロミスに変換するにはどうすればよいですか。

155
Bergi

すでに確立された答えに加えて、ES6の矢印機能で約束は適度に輝く小さな青い小人まっすぐなから赤い巨人に変わります。それは超新星に崩壊しようとしています:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

oligofren が指摘しているように、API呼び出しの間に引数がなければ、無名ラッパー関数はまったく必要ありません。

api().then(api2).then(api3).then(r3 => console.log(r3))

そして最後に、超大質量のブラックホールレベルに到達したい場合は、Promiseを待つことができます。

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}
18
John Weisz

他の答えに加えて、ES2015の構文は約束とシームレスに融合し、さらに定型コードを減らすことができます。

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});
10
Duncan Luk

上記の素晴らしい答えに加えて、さらに2つのポイントを追加することができます。

1。意味の違い:

約束は作成時にすでに解決されている可能性があります。これは、が、イベントではなく条件を保証することを意味します。それらがすでに解決されている場合、それに渡された解決済み関数はまだ呼び出されます。

逆に、callbacksはイベントを処理します。そのため、コールバックが登録される前に関心のあるイベントが発生した場合、コールバックは呼び出されません。

2。制御の反転

コールバックは制御の逆転を伴います。コールバック関数を任意のAPIに登録すると、Javascriptランタイムはコールバック関数を格納し、実行準備が整ったらイベントループから呼び出します。

説明は Javascriptイベントループ を参照してください。

Promisesを指定すると、制御は呼び出し側プログラムに常駐します。promiseオブジェクトを格納すると、.then()メソッドをいつでも呼び出すことができます

7
dww

プロミスはコールバックではなく、どちらも非同期プログラミングを容易にするプログラミングイディオムです。約束を返すコルーチンやジェネレータを使用した非同期/待機スタイルのプログラミングの使用は、そのような3番目の慣用句と見なすことができます。さまざまなプログラミング言語(Javascriptを含む)でのこれらの慣用句の比較は次のとおりです。 https://github.com/KjellSchubert/promise-future-task

4
Kjell Schubert

コールバックをラッパーするだけの約束はありません。

example jsと一緒にjavascriptのネイティブな約束を使うことができます

my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
    request.get(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        resolve(body);
    }
    else {
        reject(error);
    }
    })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
    console.log(e);
})
.then(function (result) {
    res.end(result);
}
)

})


var server = app.listen(8081, function () {

var Host = server.address().address
var port = server.address().port

console.log("Example app listening at http://%s:%s", Host, port)

})


//run webservice on browser : http://localhost:8081/listAlbums
1
Apoorv

いいえ、まったく違います。

コールバック は単に呼び出されて実行される JavaScript の関数です。他の機能の実行が終了した後。それでどうやってそれが起こるのでしょうか?

実際、JavaScriptでは、関数自体がオブジェクトと見なされるため、他のすべてのオブジェクトと見なされます。関数でさえ、他の 関数への引数として送信できます 。考えられる最も一般的で一般的なユースケースは、JavaScriptのsetTimeout()関数です。

Promise は、コールバックで同じことを行うのと比較して、非同期コードを処理して構造化するためのはるかに即興的なアプローチに他なりません。

Promiseはコンストラクタ関数で2つのCallbackを受け取ります:resolveとreject。 promise内のこれらのコールバックは、エラー処理と成功ケースに対するきめ細かい制御を提供します。 resolveコールバックはpromiseの実行が正常に実行されたときに使用され、rejectコールバックはエラーのケースを処理するために使用されます。

0
Ayush Jain