序章

JavascriptPromisesは理解するのが難しい場合があります。 したがって、私は約束を理解する方法を書き留めたいと思います。

約束を理解する

要するに約束:

「あなたが子供だと想像してみてください。 あなたのお母さんは、来週新しい電話を手に入れることを約束します。」

あなたは来週までその電話を手に入れるかどうかわからない。 あなたのお母さんはあなたに真新しい電話を本当に買うことができます、または彼女はしません。

それが約束です。 約束には3つの状態があります。 彼らです:

  1. 保留中:あなたはその電話を手に入れるかどうかわからない
  2. 満たされた:お母さんは幸せです、彼女はあなたに真新しい電話を買います
  3. 拒否されました:お母さんは不幸です、彼女はあなたに電話を買わない

約束を作成する

これをJavaScriptに変換してみましょう。

// ES5: Part 1

var isMomHappy = false;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

コード自体は非常に表現力豊かです。

以下は、promise構文が通常どのように見えるかです。

// promise syntax look like this
new Promise(function (resolve, reject) { ... } );

約束を消費する

約束ができたので、それを消費しましょう。

// ES5: Part 2

var willIGetNewPhone = ... // continue from part 1

// call our promise
var askMom = function () {
    willIGetNewPhone
        .then(function (fulfilled) {
            // yay, you got a new phone
            console.log(fulfilled);
             // output: { brand: 'Samsung', color: 'black' }
        })
        .catch(function (error) {
            // oops, mom didn't buy it
            console.log(error.message);
             // output: 'mom is not happy'
        });
};

askMom();

例を実行して結果を見てみましょう!

デモ: https://jsbin.com/nifocu/1/edit?js,console

Result

連鎖の約束

約束は連鎖可能です。

あなた、子供、あなたのお母さんがあなたに新しい電話を買ったときにあなたが彼らに新しい電話を見せることをあなたの友人に約束するとしましょう。

それは別の約束です。 書きましょう!

// ES5

// 2nd promise
var showOff = function (phone) {
    return new Promise(
        function (resolve, reject) {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

注:以下のように記述することで、上記のコードを短縮できます。

// shorten it

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

約束を連鎖させましょう。 子供であるあなたは、willIGetNewPhoneの約束の後でのみshowOffの約束を開始できます。

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
         // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
         // output: 'mom is not happy'
        });
};

それはあなたが約束を連鎖させることができる方法です。

約束は非同期です

約束は非同期です。 約束を呼び出す前後にメッセージを記録しましょう。

// call our promise
var askMom = function () {
    console.log('before asking Mom'); // log before
    willIGetNewPhone
        .then(showOff)
        .then(function (fulfilled) {
            console.log(fulfilled);
        })
        .catch(function (error) {
            console.log(error.message);
        });
    console.log('after asking mom'); // log after
}

期待される出力のシーケンスは何ですか? あなたは期待するかもしれません:

1. before asking Mom
2. Hey friend, I have a new black Samsung phone.
3. after asking mom

ただし、実際の出力シーケンスは次のとおりです。

1. before asking Mom
2. after asking mom
3. Hey friend, I have a new black Samsung phone.

Output

お母さんの約束(新しい電話)を待っている間、あなたはプレーをやめません。 これを非同期と呼びます。コードはブロックしたり結果を待ったりすることなく実行されます。 約束が進むのを待つ必要があるものはすべて.thenに入れられます。

ES5の完全な例を次に示します。

// ES5: Full example

var isMomHappy = true;

// Promise
var willIGetNewPhone = new Promise(
    function (resolve, reject) {
        if (isMomHappy) {
            var phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone); // fulfilled
        } else {
            var reason = new Error('mom is not happy');
            reject(reason); // reject
        }

    }
);

// 2nd promise
var showOff = function (phone) {
    var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

    return Promise.resolve(message);
};

// call our promise
var askMom = function () {
    willIGetNewPhone
    .then(showOff) // chain it here
    .then(function (fulfilled) {
            console.log(fulfilled);
            // output: 'Hey friend, I have a new black Samsung phone.'
        })
        .catch(function (error) {
            // oops, mom don't buy it
            console.log(error.message);
            // output: 'mom is not happy'
        });
};

askMom();

ES5、ES6 / 2015、ES7/Nextでの約束

ES5-多数派のブラウザ

Bluebird promiseライブラリを含めると、デモコードはES5環境(すべての主要なブラウザー+ NodeJ)で機能します。 これは、ES5がそのままの状態でpromiseをサポートしていないためです。 もう1つの有名なプロミスライブラリは、KrisKowalによるQです。

ES6 / ES2015-最新のブラウザー、NodeJs v6

ES6はpromiseをネイティブにサポートしているため、デモコードはそのままで機能します。 さらに、ES6関数を使用すると、矢印関数を使用してコードをさらに簡略化し、constおよびletを使用できます。

ES6コードの完全な例を次に示します。

//_ ES6: Full example_

const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => { // fat arrow
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
const showOff = function (phone) {
    const message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';
    return Promise.resolve(message);
};

// call our promise
const askMom = function () {
    willIGetNewPhone
        .then(showOff)
        .then(fulfilled => console.log(fulfilled)) // fat arrow
        .catch(error => console.log(error.message)); // fat arrow
};

askMom();

すべてのvarconstに置き換えられていることに注意してください。 すべてのfunction(resolve, reject)(resolve, reject) =>に簡略化されています。 これらの変更にはいくつかの利点があります。

ES7-非同期/待機

ES7では、asyncおよびawait構文が導入されました。 .then.catchがなくても、非同期構文を理解しやすくなります。

ES7構文で例を書き直します。

// ES7: Full example
const isMomHappy = true;

// Promise
const willIGetNewPhone = new Promise(
    (resolve, reject) => {
        if (isMomHappy) {
            const phone = {
                brand: 'Samsung',
                color: 'black'
            };
            resolve(phone);
        } else {
            const reason = new Error('mom is not happy');
            reject(reason);
        }

    }
);

// 2nd promise
async function showOff(phone) {
    return new Promise(
        (resolve, reject) => {
            var message = 'Hey friend, I have a new ' +
                phone.color + ' ' + phone.brand + ' phone';

            resolve(message);
        }
    );
};

// call our promise in ES7 async await style
async function askMom() {
    try {
        console.log('before asking Mom');

        let phone = await willIGetNewPhone;
        let message = await showOff(phone);

        console.log(message);
        console.log('after asking mom');
    }
    catch (error) {
        console.log(error.message);
    }
}

// async await it here too
(async () => {
    await askMom();
})();

約束とそれらをいつ使用するか

なぜ約束が必要なのですか? 約束の前に世界はどのように見えましたか? これらの質問に答える前に、基本に戻りましょう。

通常機能VS非同期機能

これらの2つの例を見てみましょう。 どちらの例も、2つの数値の加算を実行します。1つは通常の関数を使用して加算し、もう1つはリモートで加算します。

2つの数値を加算する通常の関数

// add two numbers normally

function add (num1, num2) {
    return num1 + num2;
}

const result = add(1, 2); // you get result = 3 immediately
2つの数値を加算する非同期関数
// add two numbers remotely

// get the result by calling an API
const result = getAddResultFromServer('http://www.example.com?num1=1&num2=2');
// you get result  = "undefined"

通常の関数で数値を加算すると、すぐに結果が得られます。 ただし、結果を取得するためにリモートコールを発行する場合は、待機する必要があり、すぐに結果を取得することはできません。

サーバーがダウンしている、応答が遅いなどの理由で、結果が得られるかどうかはわかりません。 結果を待っている間、プロセス全体がブロックされることは望ましくありません。

APIの呼び出し、ファイルのダウンロード、ファイルの読み取りは、実行する通常の非同期操作の一部です。

非同期呼び出しにpromiseを使用する必要はありません。 promiseの前は、コールバックを使用していました。 コールバックは、戻り結果を取得したときに呼び出す関数です。 前の例を変更して、コールバックを受け入れましょう。

// add two numbers remotely
// get the result by calling an API

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback
    const result = success; // you get result = 3 here
});

後続の非同期アクション

番号を1つずつ追加するのではなく、3回追加します。 通常の機能では、これを行います:-

// add two numbers normally

let resultA, resultB, resultC;

 function add (num1, num2) {
    return num1 + num2;
}

resultA = add(1, 2); // you get resultA = 3 immediately
resultB = add(resultA, 3); // you get resultB = 6 immediately
resultC = add(resultB, 4); // you get resultC = 10 immediately

console.log('total' + resultC);
console.log(resultA, resultB, resultC);

これは、コールバックでどのように見えるかです。

// add two numbers remotely
// get the result by calling an API

let resultA, resultB, resultC;

function addAsync (num1, num2, callback) {
    // use the famous jQuery getJSON callback API
	// https://api.jquery.com/jQuery.getJSON/
    return $.getJSON('http://www.example.com', {
        num1: num1,
        num2: num2
    }, callback);
}

addAsync(1, 2, success => {
    // callback 1
    resultA = success; // you get result = 3 here

    addAsync(resultA, 3, success => {
        // callback 2
        resultB = success; // you get result = 6 here

        addAsync(resultB, 4, success => {
            // callback 3
            resultC = success; // you get result = 10 here

            console.log('total' + resultC);
            console.log(resultA, resultB, resultC);
        });
    });
});

デモ: https://jsbin.com/barimo/edit?html,js,console

この構文は、コールバックが深くネストされているため、使い勝手が悪くなります。

深くネストされたコールバックの回避

Promiseは、深くネストされたコールバックを回避するのに役立ちます。 同じ例のpromiseバージョンを見てみましょう。

// add two numbers remotely using observable

let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
	// What is .json()? https://developer.mozilla.org/en-US/docs/Web/API/Body/json
    return fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json()); 
}

addAsync(1, 2)
    .then(success => {
        resultA = success;
        return resultA;
    })
    .then(success => addAsync(success, 3))
    .then(success => {
        resultB = success;
        return resultB;
    })
    .then(success => addAsync(success, 4))
    .then(success => {
        resultC = success;
        return resultC;
    })
    .then(success => {
        console.log('total: ' + success)
        console.log(resultA, resultB, resultC)
    });

promiseを使用して、.thenでコールバックをフラット化します。 ある意味では、コールバックのネストがないため、見た目はすっきりしています。 ES7 async構文を使用すると、この例をさらに拡張できます。

オブザーバブル

約束に落ち着く前に、Observablesと呼ばれる非同期データの処理に役立つ何かがあります。

Observablesで書かれた同じデモを見てみましょう。 この例では、オブザーバブルにRxJSを使用します。

let Observable = Rx.Observable;
let resultA, resultB, resultC;

function addAsync(num1, num2) {
    // use ES6 fetch API, which return a promise
    const promise = fetch(`http://www.example.com?num1=${num1}&num2=${num2}`)
        .then(x => x.json());

    return Observable.fromPromise(promise);
}

addAsync(1,2)
  .do(x => resultA = x)
  .flatMap(x => addAsync(x, 3))
  .do(x => resultB = x)
  .flatMap(x => addAsync(x, 4))
  .do(x => resultC = x)
  .subscribe(x => {
    console.log('total: ' + x)
    console.log(resultA, resultB, resultC)
  });

Observablesはもっと面白いことをすることができます。 たとえば、delayは、3 secondsによって1行のコードで関数を追加するか、再試行して、特定の回数だけ呼び出しを再試行できるようにします。

...

addAsync(1,2)
  .delay(3000) // delay 3 seconds
  .do(x => resultA = x)
  ...

私のRxJの投稿の1つここを読むことができます。

結論

コールバックとプロミスをよく理解することが重要です。 それらを理解し、使用してください。 まだObservablesについて心配する必要はありません。 状況に応じて、3つすべてを開発に含めることができます。