非同期処理
javascriptで非同期処理を書こうとした時、普通に処理を書くと上から書いた通りの実行順序では完了しません。非同期処理の部分が、往々にして遅延して処理が完了してしまうからです。
非同期処理として有名なものには ajax や setTimeout、XHR(XMLHttpRequest) が存在します。
コマンドライン上で実行する簡単な例を書くと
$ node
> console.log('hello'); console.log('world');
hello
world
undefined
> setTimeout(() => console.log('hello'), 500); console.log('world');
world
undefined
> hello
普通に連続で書くとhello
と world
が順序通り表示されますが、setTimeoutを使った方は順序が逆になってますね。
これは setTimeout で指定したものが、500ms後に実行されるからです。
非同期で実行することでページの表示速度等改善に繋げられることはありますが、
非同期処理の結果を受けてさらに処理をしたいといった場合、順序が重要になってきます。
この実行順序をjavascriptで制御するにはこれまで以下の方法がありました。
(jQueryを使っていればDeferredもありますが、今回は省きます)
- コールバック
- Promise(ES2015で追加)
そしてES2017で、async/awaitが正式に追加されました。
いずれも非同期処理を制御するものですが、当然書き方が違ってきます。
コールバックはいわゆるコールバック地獄とも呼ばれるもので、複雑なコードになりがちでした。
Promiseはそのコールバック地獄を解消するための記述方法。
そしてasync/awaitはさらにシンプルに記述するための記述方法となります。
さてここで、コールバック、Promise、async/awaitの簡単な例をとりあえず並べてみましょう。
コールバック関数
setTimeout(() => {
console.log('hello')
setTimeout(() => {
console.log('callback')
setTimeout(() => {
console.log('world')
}, 1000)
}, 200)
}, 1000)
Promise
const promise = (message, msec) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(message);
resolve();
}, msec);
});
promise('hello', 1000)
.then(() => promise('promise', 200))
.then(() => promise('world', 1000))
async/await
const promise = (message, msec) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(message);
resolve();
}, msec);
});
const printMessage = async () => {
await promise('hello', 1000)
await promise('async', 200)
await promise('world', 1000)
}
printMessage()
コールバックではネストが多いのがわかりますね。
簡単な例ですので、Promiseとasync/awaitは同じくらいシンプルに書けてるように見えます。
(async/await
でPromiseが出てきますが、これは、awaitがPromiseを同期的に展開するように見せる機能なため、ここでもPromiseが出てきたのでした。)
Promiseについてもっと詳しく
Promiseについて詳しく理解できていないと、async/awaitでも使われているPromiseのそもそもの挙動がどんなものか知りたくなるはず。
これは記事が沢山あるのでこの記事では端折りますが、、
Promiseは非同期処理を並列で実行したり、直列で実行させることで処理をつなげることができます。
また、非同期処理が成功・失敗したときの処理が明示的に書けます。このあたり詳しくは下記の記事がわかりやすいと思います。
async/awaitは何がいいのか
- チェーンする必要がなくなるため単純にコードが読みやすくなる
- 複雑な処理をしようとした場合Promiseよりもネストが深くならない
- エラー処理が簡潔になる
など。Promiseよりもコードが直感的にわかりやすく書けるようになります。
上のコードを少しだけ複雑にした例を出してみます。
const promise = (message, msec) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(message);
resolve(message);
}, msec);
});
promise('hello', 1000)
.then(res1 => {
const message = res1 + 'promise'
promise(message, 500).then(
res2 => {
const message = res2 + 'world'
promise(message, 2000)
})
})
const promise = (message, msec) => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(message);
resolve(message);
}, msec);
});
const printMessage = async () => {
const res1 = await promise('hello', 1000)
const message1 = res1 + 'async'
const res2 = await promise(message1, 500)
const message2 = res2 + 'world'
const res3 = await promise(message2, 2000)
}
printMessage()
上記の例のように非同期処理の結果を使いまわすような場合、async/await
のほうがシンプルで見通しのよい記述ができていることがわかると思います。
終わりに
非同期処理をさせるときjQueryでDeferredを使うことが多かったので改めて非同期処理に関して勉強し直しました。
async/awaitでシンプルに書けるなと感じたので、今後はasync/awaitを積極的に使っていきたいところです
また、Node ver7.6以降は、async/awaitがデフォルトでサポートされるためCLIで実行するときに--harmony-async-await
という長ったらしいオプションは不要になっています。時間があれば試しに簡単なスクリプトを作って遊ぶのもよいかなと思います。
■ 参考記事
Promiseとasync/awaitでJavaScriptの非同期処理をシンプルに記述する
JavaScriptのasync/awaitの概要と基本的な使い方
AsyncとAwait : コールバック地獄を避けるための最新のやり方、そしてその未来
JavaScriptのasync/awaitがPromiseよりもっと良い