はじめに Promise
とは
JavaScriptの非同期処理は素晴らしいです、HTTPリクエストやDBの参照など待ち時間の発生する処理が入っても不必要に待つ必要はありません。言葉を変えると待ってくれません。
// A
console.log(1)
// B
setTimeout(() => {
console.log(2)
}, 1000)
// C
console.log(3)
1
3
2 #1秒後
B
のsetTimeout()
内が実行されるのを待たずC
が実行されるため
A
→C
→1秒後にB
のsetTimeout
内の処理 が実行されます。
AjaxのレスポンスやDBの問い合わせ結果が返ってくるまでがsetTimeout()
に置き換わるイメージです。
コールバック関数内に次の処理を書けば意図した動作にはなるのですが、何度も繰り返すとネストが深くなりコールバック地獄になる。
setTimeout(()=> {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 1000)
}, 1000)
}, 1000)
1 #1秒後
2 #さらに1秒後
3 #さらに1秒後
Promise
を使うとネストせずに書ける。
let A = () => {
return new Promise(resolve => setTimeout(() => {
console.log(1)
resolve()
}, 1000))
}
let B = () => {
return new Promise(resolve => setTimeout(() => {
console.log(2)
resolve()
}, 1000))
}
let C = () => {
return new Promise(resolve => setTimeout(() => {
console.log(3)
resolve()
}, 1000))
}
let promise = Promise.resolve()
promise.then(A).then(B).then(C)
1 #1秒後
2 #さらに1秒後
3 #さらに1秒後
しかしpromise.then(A).then(B).then(C)
と指定しては再帰やループが出来ない。
出来たとしても実装が難しい。これはHaskellなど関数型言語の思考に強く影響を受けてるからだとか。
(※書き方を変えれば可能です、後述のURLにその方法が記載されています)
今回紹介したのは単純な逐次処理ですが、複数の非同期処理が全て終了したあとに実行する.all()
や、どれか1つ終了したあとに実行する.race()
など色々なパターンが存在するので、Promise
を詳しく学びたいという方は下記のページがを参考にしてください。
http://azu.github.io/promises-book/
そのほかQiitaにも他の方が数多く解説記事を投稿しています。
Async
/ Await
の紹介
Promise
だけだと実装が難しい場合などに便利なのがAsynx
/ Await
です。
const sleep = (n) => {
return new Promise(resolve => setTimeout(() => {
console.log(n);
resolve()
}, 1000))
}
(async () => {
for (i of [1,2,3]) {
await sleep(i);
}
})()
1 #1秒後
2 #さらに1秒後
3 #さらに1秒後
sleep()
の部分がPromise
を紹介したときのコードとほぼ変化がないのが分かるかと思います。
ちなみにAsync
/ Await
を使う場合は[].forEach()
が使えません、今回のコードではfor (i of [1,2,3]){ }
で代用しています。
Async
で囲んだ範囲でAwait
が使用できます。
先頭にawait
を指定して関数を実行することにより、関数内のPromise
がresolve()
を返した時に次の処理が実行されるようになります。
for
で囲んでしまいましたが下記のコードと同等です。
(async () => {
await sleep(1);
await sleep(2); // 1秒後に実行される
await sleep(3); // さらに1秒後に実行される
})()
Async
/ Await
を使うことによってPromise
のハードルが低くなり扱いやすくなると思うので、Promise
って難しそう、苦手という方にこそ先にこんなのもあると知ってもらいたいです。