現代的JavaScriptおれおれアドベントカレンダー2017 – 11日目
概要
Promise
を使った非同期の処理をさくさく書けるやつです。
簡単な例としては、まず Promise
オブジェクトを返す関数↓があったとして、
// 指定ms待つ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
このコード↓を
function sayAfterSleeping(message) { console.log('ぐー……'); sleep(1000) .then(() => { console.log('むにゃ……'); return sleep(1000); }) .then(() => { console.log('あ、ごめん寝てた。えっと……'); return sleep(1000); }) .then(() => { console.log(message); }) } sayAfterSleeping('Hello Async World!');
こう↓書けます。
async function sayAfterSleeping(message) { console.log('ぐー……'); await sleep(1000); console.log('むにゃ……'); await sleep(1000); console.log('あ、ごめん寝てた。えっと……'); await sleep(1000); console.log(message); } sayAfterSleeping('Hello Async World!');
基本的な使い方
Promise
を使ったコードを、前述のようにそれなりに簡単に置き換えることができます。
Promise
を返す関数の呼び出しにawait
を付けるawait
を使う関数にasync
を付ける
これだけです。
忘れないでね
async
を付けるのは Promise
オブジェクトを返す関数ではなく、 await
を使う方の関数という点にご注意ください。逆に Promise
オブジェクトを返すやつに async
を付ける必要は(その中で await
を使っていないなら)ないです。
あ、あと await
を書けるのは async
を付けた関数の中だけです。
ここら辺最初のころは忘れたり混同したりしがちでは。(個人の感想です。)
結果を受け取る
then()
で登録するコールバック関数実行時に結果を受け取ることができましたが、 await
の場合は単純な戻り値のような形で扱えます。
// fetch()とformat()はダミーです function fetchDetail(id) { return fetch(`/api/foo/${id}`) // Promiseを返す .then((res) => { return format(res); // 結果を整形 }); } async function showDetail(id) { const detail = await fetchDetail(id); // 待ってから結果を受け取る console.log(detail.name, detail.type); } showDetail(123);
Fetch APIの話じゃないです。
for
文
実践編。
間を置きながら n
回数えて終了する countdown()
という処理を考えます。
真ん中が本体です。
// 指定ms待つ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function countdown(n) { let p = Promise.resolve(); for (let i = 0; i < n; i++) { p = p.then(() => { console.log(`${n - i}...`); return sleep(1000); }); } return p; } countdown(3) .then(() => { console.log('done!') })
なんかややこしいすね。
単純な for
文だと駄目です。だって Promise
完了まで待たないとすぐ終わっちゃうから。なので、 Promise
オブジェクトを変数 p
で覚えておいて、さらにそこから then()
の戻り値で上書きしながら連結します。(ちなみに let
を var
にするとだめです。)
これ↑を、 await
を使うことで、こう↓めっちゃ簡単に書けるようになります。
// 指定ms待つ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function countdown(n) { for (let i = 0; i < n; i++) { console.log(n - i, '...'); await sleep(1000); } } countdown(3) .then(() => { console.log('done!') })
ほら、さっき駄目だった「ただの for
文」で済んでしまいました。読みやすーい。
宣言、式、メソッド
各種取り揃えております。
async function foo() {} const bar = async function() {}; const obj = { async hoge() {}, }; class Klass { async fuga() {} }
例外
Promise
オブジェクトの catch()
は、普通の try-catch
で実現できます。
async function() { try { const result = await doAsync(); } catch (err) { console.error(err); } }
いろいろな await
途中に await
わりとどこにでも await
を突っ込むことができるようです。
function returnLater(result) { return new Promise(resolve => setTimeout(() => resolve(result), 1000)); } async function af () { console.log('...'); console.log(await returnLater(1) + await returnLater(2)); // 3 } af();
ただ await
出現の時点で待機を開始するので、複数書いても並列に実行はしません。上記の例では1000 ms待つのを二回呼んでるので、結果が出てくるまで2000 msかかります。
await new Promise()
というのもできます。要は右辺が Promise
オブジェクトなら良いわけで、関数実行は結果としてそれを満たすだけです。
async function foo() { await new Promise(resolve => setTimeout(resolve, 1000)); console.log('おまたせ!'); }
await 123
というのもできます。右辺が Promise
オブジェクトだったら待つんだけど、そうでなければそのまま流します。
await await await p
というようにたくさん並べることもできます。
できるけどしないね。(関数呼び出しの中身を追っていくと結果的にこうなる場面はいくらでもあるだろうけど。)
ES2017
ES2015 (ES6)じゃなくてES2017で追加されました。
ChromeとFirefoxではもう使えます。それ以外で使うならバベってください。
その他
async
後に改行は駄目
だめです。
async function foo() {} // Exception: ReferenceError: async is not defined
まあこんなのしないよね。
Function
オブジェクトではない
async
付きで宣言したものは、コンストラクタが Function
ではなく AsyncFunction
になります。
console.log((async function(){}).constructor); // => function AsyncFunction()
とはいえ AsyncFunction
が Function
を継承しているので、 instanceof
は普通に動きます。ご安心ください。
console.log((async function(){}) instanceof Function); // => true
AsyncFunction
コンストラクタ
前項の通り、 AsyncFunction
コンストラクタは存在するんだけど、グローバルオブジェクトにはなってません。
const af = new AsyncFunction(); // Exception: ReferenceError: AsyncFunction is not defined
どうしても使いたければ、インスタンスから探っていきます。
const AsyncFunction = (async function(){}).constructor; const af = new AsyncFunction();
使い方は Function
といっしょ。
“async” の読み方
「アシンク」より「エイシンク」くらいっぽいです。まあ日本語で会話してるところにエィスィ↑ンクとか言っても逆に通じないだろうけど。
英単語 “async”, “await” の意味
“async[hronous]” は「非同期」。皆様ご存知ですね。
“await” は「待ち受ける」だそうです。
参考
- ECMAScript® 2018 Language Specification
- 14.6 Async Function Definitions
- async function – JavaScript | MDN
- await – JavaScript | MDN
- AsyncFunction – JavaScript | MDN … コンストラクタの方