[Javascript] Promiseを使った非同期処理入門まとめ

[Javascript] Promiseを使った非同期処理入門まとめ

Promise とは

Promise とは、Javascriptでの非同期処理の完了を待ってその処理結果を取得するためのオブジェクトです。いわゆるコールバック地獄に陥らずスマートに非同期処理を掛けるのがメリットとしてあります。

Promise による非同期処理

// Promiseオブジェクトを生成し、非同期処理を実行する
var p = new Promise(function(resolve, reject) {
    // 非同期で行いたい処理
    try {
        // 処理結果を返すのは resolve を実行
        // 非同期処理が正常終了したことを伝える
        resolve('Success ...');
    } catch (error) {
        // 処理が失敗したことを伝えるには reject を実行
        reject('Error ...');
    }
});

p.then(function(value) {
    // 非同期処理が成功した場合
    console.log(value); // Success ...
}).catch(function(value) {
    // 非同期処理が失敗した場合
    console.log(value); // Error ...
});

Promise オブジェクトの生成

Promiseオブジェクトは次のような構文を使います。Promiseコンストラクタに非同期に実行したい処理を関数で渡します。

new Promise(function(resolve, reject) { /*ここに非同期処理を記述*/ } );

Promiseオブジェクトを上記構文で生成すると、そのタイミングで引数の関数が非同期的に実行されます。どんなに時間がかかる処理を指定してもすぐに次の処理に進みます。

非同期処理の完了は、非同期処理を指定する関数の引数で渡される resolve, reject という関数を実行することで表現できます。

一例として、trycatch でエラーになると reject 関数を実行し、非同期処理の失敗を通知し、成功したときは resolve 関数で成功を通知しています。resolve, reject の両関数は引数を指定できます。この引数が非同期処理の処理結果(戻り値)となります。この例では文字列で指定していますが、任意のオブジェクトで好きな値を返せます。

もちろん trycatch を使わなくても、任意の条件で成功、失敗の通知が可能です。

then で成功通知を受け取る

生成された Promise オブジェクトの then 関数を実行することで、非同期処理の成功時(resolveが実行された時)の通知を受け取ることが可能です。then 関数の引数で通知を受け取った際に実行するコールバック関数を指定します。

// 2秒後に配列[1, 2, 3]を返す非同期処理
var p = new Promise(function(resolve) {
    setTimeout(function() { resolve([1, 2, 3]); }, 2000);
});

// 非同期処理の成功時の通知を then で受け取る
p.then(function(value) {
    // 非同期処理成功時の処理を記述
    // 引数で戻り値を受け取れる
    console.log(value);
});

console.log('console end ...');

// console end ...
// [1, 2, 3]

2秒後に処理が完了し、配列を返す非同期処理があったとします。そのような処理を Promise で実行します。成功を待ってから実行する処理は then 関数に記述します。

then 関数内の処理は非同期処理の成功を待機してから実行されるので、この例だと約2秒後に実行されます。ただしこの間に Javascript の同期処理が止まることはないので、最後に書いたコンソールへの出力処理が先に実行されることになります。

この then 関数はチェーンして数珠つなぎにすることが可能です。非同期処理の後に 処理1, 処理2, 処理3 と処理を続けて実行する場合に次のように記述できます。

new Promise(function(resolve) {
    // 非同期処理
    setTimeout(function() {
        resolve(0);
    }, 2000);
})
.then(function(num) {
    // 処理1: 非同期処理が完了(成功)したら実行される
    num++;
    console.log('処理1:', num);
    return num;
})
.then(function(num) {
    // 処理2: 1が終わったら実行される
    return new Promise(function(resolve) {
        // 非同期処理
        setTimeout(function() {
            num++;
            console.log('処理2:', num);
            resolve(num);
        }, 2000);
    });
})
.then(function(num) {
    // 処理3: 2が終わったら実行される
    num++;
    console.log('処理3:', num);
});

// 処理1: 1
// 処理2: 2
// 処理3: 3

then 関数が前の処理の実行結果を引数で受け取って順次処理が実行されていることが確認できます。処理2はPromiseオブジェクトを返す非同期処理ですが、続く処理3の then 関数ではその実行を待ってから実行されていることが確認できます。

then 関数を使うことでコールバック関数をたくさん使った書き方をせずともすっきりと書くことができました。

catch で処理失敗時の通知を受け取る

非同期処理は常に成功するわけではありません。例えばWebAPIを実行する例を考えると、通信エラーやサーバー内部でのエラーなど様々な要因で成功時の処理とは別に、処理を分岐させなければならないことが多いです。

Promiseオブジェクトの catch 関数は処理失敗時(rejectが実行された時)の通知を受け取ることが可能です。使い方は then と同じような感じです。

new Promise(function(resolve, reject) {
    // 非同期処理
    reject('非同期処理が失敗しました。');
})
.then(function(value) {
    console.log('Success:', value);
})
.catch(function(value) {
    // 非同期処理失敗時
    console.log('Error:', value);
});

// Error: 非同期処理が失敗しました。

このコードでは、Promise内の非同期処理で reject を実行することで、失敗を通知しています。処理が失敗しているので then 関数は実行されず、catch 関数内の処理に飛びます。

then 内でエラーが発生してスローされても catch に飛んできます。つまり、catch 前のどこかでエラーが発生すると通知が飛んでくることになります。

Promise.all で複数の非同期処理の完了通知を受け取る

ここまで見てきた例は、1つの非同期処理を待機して同期処理とうまく組み合わせる基本的な使い方です。例えば複数の非同期処理を並列に実行し、そのすべてが完了するまで待機するということも Promise.all 関数を使うことで可能です。

例えばそれぞれ 2秒, 3秒 で終わる処理(A, B)があったとします。Aの処理を待ってからBの処理を実行するというのはすでに then 関数を使って実現できる例を示しましたが、今回はABを並列に実行し、両方の実行が完了した際に処理を実行する、という例です。

// 非同期処理A
var a = new Promise((resolve) => {
    setTimeout(() => {
        resolve('A');
    }, 2000);
});
// 非同期処理B
var b = new Promise((resolve) => {
    setTimeout(() => {
        resolve('B');
    }, 3000);
});

// AB両方の完了を待機する
Promise.all([a, b]).then((results) => { 
    console.log(results) // ['A', 'B']
}).catch((error) => {
    // いずれかの非同期処理でエラーが発生すると
    // その時点で飛んでくる
});

複数のPromiseオブジェクトを配列にして Promise.all 関数に引数で渡します。Promise.all 関数は、そのすべての実行が成功するのを待って、then 関数に通知を飛ばしてくれます。いずれかの処理でエラーが発生すると catch に通知が飛びます。

アロー関数の表記にしてみましたが同じ意味です。

Promise.race で最初の通知のみを受け取る

Promise.race は面白い機能で、複数のPromiseすべての完了を待機するのではなく、一番早くに完了したPromiseの結果を優先します。つまり、最初に完了した処理が成功していれば then 関数が実行され、あるいは最初の完了が失敗の場合は catch 関数が実行されます。

以下の例では複数の非同期処理を実行していますが、1番最初に完了するのは p2 ですので、これが完了したタイミングで then 関数が実行されることが確認できます。p3 については reject されているのでエラーですが、最初の通知が優先されるため、catch には飛びません。

// 2秒後に成功
var p1 = new Promise((resolve) => {
    setTimeout(() => {
        resolve(1);
    }, 2000);
});
// 0.1秒後に成功
var p2 = new Promise((resolve) => {
    setTimeout(() => {
        resolve(2);
    }, 100);
});
// 5秒後に失敗
var p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        // 失敗しても成功先勝ちになる
        reject(3);
    }, 5000);
})

// AB両方の完了を待機する
Promise.race([p1, p2, p3]).then((value) => {
    // 複数のPromiseで1番速い通知が成功の場合、その結果
    console.log(value); // 2
}).catch((error) => {
    // 複数のPromiseで1番速い通知が失敗の場合、その結果
});

まとめ

Promise は様々なライブラリ等で利用されているため、使う機会も多いのでしっかりマスターしたいところです。また Promise をベースにさらに発展形の asynct, await もあるので、こっちも知っておくとよいでしょう。

以上。

Javascriptカテゴリの最新記事