コールバックだけを約束しませんか?


430

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

どちらがより多くのコードであり、読みにくくなります。ここでは何も得られませんでしたが、突然魔法のように「フラット」になったわけでもありません。言うまでもなく、物事を約束に変換する必要があります。

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


11
オントピック:Html5RocksのPromiseに関する本当に有益な記事があります:html5rocks.com/en/tutorials/es6/promises
ComFreek

2
あなたが受け入れた答えは、約束のポイントではなく、約束を使用するように私を説得しさえしなかった些細な利点の同じ古いリストです:/。Oscarの回答
Esailija 2014年

@Esailija元気です、あなたのリートは私を確信しました。私は他の答えを受け入れましたが、ベルギの答えはいくつかの本当に良い(そして異なる)ポイントを上げていると思います。
Benjamin Gruenbaum 14

@Esailija「Oscarの回答に記載されているように、私が約束を使用することを確信したのはDSLの側面でした」<<「DSL」とは何ですか?そして、あなたが言及している「DSLの側面」とは何ですか?
monsto 2018

1
@monsto:DSL:ドメイン固有言語。システムの特定のサブセットで使用することを目的として設計された言語(例:データベースと通信するSQLまたはORM、パターンを検索する正規表現など)。この文脈で「DSL」はPromiseのAPIであり、Oscarのようにコードを構造化すると、非同期操作の特定のコンテキストに対処するためにJavaScriptを補足する構文上の砂糖のようなものになります。プロミスは、プログラマーがこのタイプの構造のややとらえどころのない精神的な流れをより簡単に把握できるように設計されたほとんどの言語にそれらを変えるいくつかのイディオムを作成します。
Michael Ekoka

回答:


631

約束はコールバックではありません。promise は、非同期操作の将来の結果を表します。もちろん、あなたのやり方でそれらを書いても、ほとんど利益がありません。しかし、意図したとおりにそれらを記述すると、同期コードに似た方法で非同期コードを記述でき、追跡がはるかに簡単になります。

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の表記法を使用したpromiseは、別の簡単なものであり、非常にすっきりしています。

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

あなたが今、新しい見方で約束を見るのを望みます。


124
彼らは本当にそれを「約束」と名付けるべきではなかった。「未来」は少なくとも100倍優れています。
Pacerier

12
@Pacerierは、FutureがjQueryに汚染されていないためですか?
エサイリヤ2015年

5
代替パターン(必要なものに応じて:api()。then(api2).then(api3).then(doWork);つまり、api2 / api3関数が最後のステップから入力を受け取り、新しいプロミス自体を返す場合、ただ、余分なラッピングせずに連鎖させることができ、彼らが作曲、すなわち。。
Dtipson

1
に非同期操作がある場合はどうapi2なりapi3ますか?最後の.then呼び出しは、これらの非同期操作が完了したときにのみ呼び出されますか?
NiCkニューマン

8
なぜ私にタグを付けたのですか?文法を少し直したところです。私はJSの専門家ではありません。:)
Scott Arciszewski

169

はい、Promiseは非同期コールバックです。それらはコールバックができないことを何もすることができません、そしてあなたは普通のコールバックと同じように非同期で同じ問題に直面します。

しかし、約束がある以上、単にコールバックより。これらは非常に強力な抽象化であり、エラーが発生しにくいボイラープレートを使用して、よりクリーンで優れた機能的なコードを実現します。

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

Promiseは、単一の(非同期)計算の結果を表すオブジェクトです。彼らは一度だけその結果に解決ます。これが意味することはいくつかあります:

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

  • タスクが完了する前に、値を使用するコールバックを知っている必要はありません。
  • 関数の引数としてコールバックを期待する代わりにreturn、Promiseオブジェクトを簡単に作成できます
  • promiseは値を保存し、いつでも透過的にコールバックを追加できます。結果が利用可能になると呼び出されます。「透明性」は、promiseがあり、それにコールバックを追加しても、結果がまだ届いているかどうかに関係なくコードに影響を与えないことを意味します。APIとコントラクトは同じで、キャッシング/メモ化が大幅に簡素化されます。
  • 複数のコールバックを簡単に追加できます

約束はチェーン可能ですモナドあなたがしたい場合):

  • プロミスが表す値を変換する必要がある場合は、変換関数をプロミスにマッピングし、変換された結果を表す新しいプロミスを取得します。同期して値を取得して何らかの方法で使用することはできませんが、Promiseコンテキストで変換を簡単に解除できます。ボイラープレートコールバックはありません。
  • 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 */);

これらのメソッドのコードを確認すると理解に役立つ場合は、数行で最も基本的なpromiseライブラリを次に示します。

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

Promiseの抽象化により、関数の構成性が大幅に向上します。たとえば、thenチェーンの隣にあるall関数は、複数の並列待機プロミスの結果を組み合わせたプロミスを作成します。

最後になりましたが、Promiseにはエラー処理が統合されています。計算の結果は、promiseが値で満たされるか、または理由で拒否されるかのいずれかです。すべての構成関数はこれを自動的に処理し、プロミスチェーンでエラーを伝播するため、プレーンコールバックの実装とは対照的に、どこでも明示的に気にする必要はありません。最後に、発生したすべての例外に対して専用のエラーコールバックを追加できます。

言うまでもなく、物事を約束に変換する必要があります。

これは、実際にはプロミスライブラリでは非常に簡単です。「既存のコールバックAPIをプロミスに変換するにどうすればよいですか?」を参照してください


こんにちは、ベルギ、このSOの質問に何か興味深いことはありますか?stackoverflow.com/questions/22724883/...
セバスチャン・ローバー

1
@セバスチャン:私はスカラ(まだ)についてあまり知りません、そしてベンジャミンが言ったことを繰り返すことしかできません:-)
ベルギ

3
ほんの少しの注意:.then(console.log)console.logはコンソールのコンテキストに依存するため、は使用できません。このようにすると、不正な呼び出しエラーが発生します。console.log.bind(console)またはx => console.log(x)を使用してコンテキストをバインドします。
タマスHegedus

3
@hege_hegedus:consoleメソッドが既にバインドされている環境があります。そしてもちろん、私は両方のネスティングがまったく同じ振る舞いを持っていると述べ、それらのいずれかが:-P働くだろうではないこと
Bergi

1
すごく良かった。これが私が必要としていたことです。コードを減らして解釈を増やします。ありがとうございました。
アダムパターソン

21

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

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

以下のようoligofrenは指摘し、API呼び出しの間に、引数なしですべての匿名のラッパー関数を必要としません。

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

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

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

    return api3Result;
}

9
「ES6と機能の約束はストレート巨大な赤に控えめに輝く小さな青い星から回し矢印超新星に崩壊しようとしていること」の翻訳を:約束と機能矢印ES6を組み合わせることで素晴らしいです:)
user3344977

3
そのため、プロミスは宇宙の大災害のように聞こえますが、あなたの意図ではなかったと思います。
Michael McGinnis 2017年

apiXメソッドで引数を使用しない場合は、矢印関数を完全にスキップすることもできますapi().then(api2).then(api3).then(r3 => console.log(r3))
オリゴフレン2017年

@MichaelMcGinnis-鈍いコールバック地獄へのプロミスの有益な影響は、宇宙の暗いコーナーで爆発する超新星のようなものです。
ジョンワイズ

詩的にはそうかもしれませんが、約束は「超新星」とはかけ離れています。モナドの法則に違反したり、キャンセルや複数の値を返すなど、より強力なユースケースのサポートが不足したりすることを思い出します。
Dmitri Zaitsev

15

上記の素晴らしい答えに加えて、さらに2つのポイントが追加される場合があります。

1.意味の違い:

約束は作成時にすでに解決されている場合があります。つまり、イベントではなく条件を保証します。それらがすでに解決されている場合、それに渡された解決済みの関数は引き続き呼び出されます。

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

2.制御の反転

コールバックには、制御の反転が含まれます。APIでコールバック関数を登録すると、JavaScriptランタイムはコールバック関数を格納し、実行の準備ができたらイベントループから呼び出します。

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

約束、制御が呼び出しプログラムに存在します。.then()メソッドは、promiseオブジェクトを格納している場合はいつでも呼び出すことができます。


1
理由はわかりませんが、これはより良い答えのようです。
radiantshaw

13

他の答えに加えて、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
});

5

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


5

いいえ、まったくありません。

コールバックは単にJavaScriptの関数ですあり、呼び出され、別の関数の実行が終了した後に実行されます。それでどうやって起こるの?

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

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

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


2

コールバックのラッパーであるPromiseはありません

例ノードjsでjavascriptネイティブpromiseを使用できます

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

JavaScript Promiseは、実際にはコールバック関数を使用して、Promiseが解決または拒否された後に何をすべきかを決定します。したがって、どちらも基本的に違いはありません。Promiseの背後にある主なアイデアは、コールバックを取ることです。特に、一種のアクションを実行する必要があるネストされたコールバックですが、より読みやすくなります。


0

約束の概要:

JSでは、非同期操作(データベース呼び出し、AJAX呼び出しなど)をpromiseにラップできます。通常、取得したデータに対して追加のロジックを実行します。JS promiseには、非同期操作の結果を処理するハンドラー関数があります。ハンドラー関数には、以前の非同期操作の値に依存する可能性のある他の非同期操作を含めることもできます。

約束には常に次の3つの状態があります。

  1. 保留中:すべての約束の開始状態。履行も拒否もされません。
  2. 完了:操作は正常に完了しました。
  3. rejected:操作は失敗しました。

保留中のプロミスは、値で解決/フルフィルまたは拒否できます。次に、引数としてコールバックを取る次のハンドラーメソッドが呼び出されます。

  1. Promise.prototype.then() :promiseが解決されると、この関数のコールバック引数が呼び出されます。
  2. Promise.prototype.catch() :promiseが拒否されると、この関数のコールバック引数が呼び出されます。

上記のメソッドはコールバック引数を取得するスキルですが、コールバックのみを使用するよりもはるかに優れています。ここでは、多くのことを明確にする例を示します。

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}

createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • createProm関数は、1秒後にランダムなNrに基づいて解決または拒否されるpromiseを作成します
  • promiseが解決さthenれると、最初のメソッドが呼び出され、解決された値がコールバックの引数として渡されます
  • promiseが拒否された場合、最初のcatchメソッドが呼び出され、拒否された値が引数として渡されます
  • catchthen方法は、我々はそれらをチェーンことができる理由の約束を返します。これらは、に戻り値Promise.resolveと(throwキーワードを使用して)スローされた値をにラップしPromise.rejectます。したがって、返された値はすべてpromiseに変換され、このpromiseでハンドラー関数を再び呼び出すことができます。
  • プロミスチェーンは、ネストされたコールバックよりも細かく調整された制御と優れた概要を提供します。たとえば、catchメソッドはcatchハンドラの前に発生したすべてのエラーを処理します。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.