JavaScriptの非同期処理を整理した(Promise、Deferred、async/await)

JavaScriptの非同期処理を整理した(Promise、Deferred、async/await)



Promise

Promiseは、JavaScriptの非同期処理機能である。


基本的な使い方(thenで受ける)

  • Promiseオブジェクトを生成
    • 時間のかかる関数をコンストラクタの引数に渡す
    • 渡す関数はresolve、rejectを引数にとり、成功/失敗にあわせてそのメソッドを呼ぶ
  • 時間のかかる処理が
    • 正常に終わりresolveが呼ばれたとき→thenメソッドの第一引数の関数が実行される
    • 異常終了でrejectが呼ばれたとき→thenメソッドの第二引数の関数が実行される
let p = new Promise((resolve, reject) => {
    //時間のかかる処理
    setTimeout(() => {
        resolve();
    }, 2000);
    console.log('myfunc called');
})
.then(() => {
    console.log('fulfilled');
},
() => {
    console.log('rejected');
});


基本的な使い方(thenとcatchで受ける)

thenでresolveとrejectの両方受けると読みにくいので、以下のようにthenでresolveを、catchでrejectを受けるとちょっと読みやすい。

let p = new Promise((resolve, reject) => {
    //時間のかかる処理
    setTimeout(() => {
        reject();
    }, 2000);
    console.log('myfunc called');
})
.then(() => {
    console.log('fulfilled');
})
.catch(() => {
    console.log('rejected');
});


直列実行(thenのメソッドチェーン)

thenは複数チェーンで繋げることができる。時間のかかる複数の処理を順番に実行することができる。

let p = new Promise((resolve, reject) => {
    //時間のかかる処理
    setTimeout(() => {
        resolve();
    }, 2000);
    console.log('myfunc called');
})
.then(() => {
    //ここに時間のかかる処理を書く
    console.log('fulfilled 1');
})
.then(() => {
    console.log('fulfilled 2');
})
.catch(() => {
    console.log('rejected');
});
myfunc called
fulfilled 1
fulfilled 2

上記は、最初のPromiseオブジェクトをどんどん伝搬させていくので、最初の処理が問題なければ以降すべてのthenが実行される。

2つ目以降の処理で失敗したときにこのチェーンを抜ける場合は、thenメソッド内で別途Promiseオブジェクトを生成し、実行結果に応じてresolve/rejectを設定してやればよい。


並列実行(複数のPromiseを待つ)

例えば3つの処理すべてが終わるのを待つ場合、Promise.allを使う。

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 2000, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values);
});
Array [3, 42, "foo"]


例外処理

Promiseを呼び出す部分での例外処理。

  • try/catchで囲い、catchしたらrejectを呼ぶ。
return new Promise((resolve, reject) => {
    try {
        throw new Error();
        resolve();
    } catch(e) {
        reject(e);
    }
});


Promise受ける側での例外処理(すでに上記に書いたものの再掲)

  • catchメソッドを書く
.then((v) => {
    return v;
})
.catch((e) => {
    throw e;
});


参考URL:

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all


Deferred

Deferredは、jQueryにおける非同期処理の機構である。Promiseを包含している。


  • Deferred()でDeferredオブジェクトを生成する
  • 時間のかかる処理が
    • 正常に終わったときにresolve()メソッドを呼ぶ→done()で定義した処理が実行される
    • 失敗したときにreject()を呼ぶ→fail()で定義した処理が実行される
   let df = $.Deferred();
    df.done(() => {
        console.log('done');
    });
    df.fail(() => {
        console.log('fail');
    });

    //時間のかかる処理
    setTimeout(() => {
        df.resolve();
    }, 2000);


参考URL:

https://techblog.yahoo.co.jp/programming/jquery-deferred/#%E7%94%A8%E8%AA%9E%E3%81%AE%E6%95%B4%E7%90%86%EF%BC%9Adeferred%E3%81%A8promise


async/await

async/awaitは、JavaScriptの非同期処理機能である。

Promiseよりも簡潔に書ける一方で、一部のブラウザでは対応していない(非対応:IE11、Edge14など)。


asyncの使い方

  • 関数の頭にasyncをつけるとPromiseオブジェクトを返す
    • 正常終了の場合はreturnする→thenメソッドが実行される
    • 異常終了の場合はErrorをthrowする→catchメソッドが実行される


正常終了の例:

async function myfunc() {
    return 'resolved';
}

myfunc().then((v) => {
    console.log(v);
});


異常終了の例:

async function myfunc() {
    throw new Error('rejected');
}

myfunc().catch((e) => {
    console.log(e.message);
});


ただ、基本的にはasyncとawaitはセットで使うため、下記async/awaitの使い方を参照するとよいです。


async/awaitの使い方

  • 時間のかかる関数でPromiseオブジェクトをreturnする
  • asyncのついた関数内でawaitをつけてその関数を呼ぶと、時間のかかる関数を待つ
  • asyncの関数に応じてthenメソッドが呼ばれる
function myfunc() {
    return new Promise(resolve => {
        setTimeout(() => resolve('done'), 2000);
    });
}

async function myasyncfunc() {
    let ret = await myfunc();
    return ret;
}

myasyncfunc().then((v) => {
    console.log(v);
});


直列処理

時間のかかる処理1が終わったら時間のかかる処理2を実行、それが終わったら・・・というふうに、順番に実行していくパターン。

function myfunc(v) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('done' + v);
            resolve();
        }, 2000);
    });
}

async function myasyncfunc() {
    let ret1 = await myfunc('1');
    let ret2 = await myfunc('2');
    let ret3 = await myfunc('3');
    return 'all done';
}

myasyncfunc().then((v) => {
    console.log(v);
});
(2秒待つ)
done1
(2秒待つ)
done2
(2秒待つ)
done3
all done


並列処理

Promise.allで待つことで並列に実行し、すべての結果を待つことができる。

function myfunc(v) {
    return new Promise(resolve => {
        setTimeout(() => {
            console.log('done' + v);
            resolve(v);
        }, 2000);
    });
}

async function myasyncfunc() {
    const [a, b, c] = await Promise.all([myfunc('1'), myfunc('2'), myfunc('3')]);
    return [a, b, c];
}

myasyncfunc().then(([a, b, c]) => {
    console.log(a, b, c);
});
(2秒待つ)
done1
done2
done3
1 2 3


例外処理

Promise生成部分はPromiseの章に記載したのと同じ。


awaitで受ける部分の例外処理

  • asyncの関数内は、myfunc呼び出しで例外が発生したらそのまま処理中断される
  • そのエラーはcatchで受けられる
async function errfunc() {
    const ret = await myfunc();
    return ret;
}

errfunc().catch((e) => {
    console.log(e);
});


参考URL:

https://qiita.com/soarflat/items/1a9613e023200bbebcb3