以前も async, await, Promise 関係の記事を書いたことがあるのですが、「中で何をやっているかを何となく分かるように」書いた記事だったので、今回は「外から見てどうやって使うかを何となく分かるように」書きたいと思います。
参考「Promise, async, await がやっていること (Promise と async は書き換え可能?) - Qiita」
1. promise は整理券のようなもの
非同期処理は、「処理を開始するが、いつ終わるか分からない」ような処理のことです。
promise は、非同期処理の「整理券」と考えると分かりやすいと思います。
例えば、通信してコンテンツを取得する fetch()
は promise を返しますが、これは「コンテンツを取得する」という注文を受けて通信を開始するものの、いつ終了するか分からないため、promise という整理券を返します。
await promise
をすることで、その整理券の注文の処理が完了するのを待って、コンテンツを受け取ります。
await
を使うということは、await
が記述されているプログラムそのものもいつ終了するか分からないため、promise にする必要があります。
await
が中に記述されている関数 (アロー関数を含む) に async
を付けると、promise を返すようになります。
async
が付けられた関数を async
関数と呼びます。
//
const getJsonSync = url => {
const response = fetchSync(url);
const json = response.jsonSync();
return json;
};
//
const json = getJsonSync('https://example.com/get/something');
console.log(json.something);
//
const getJson = async url => { // getJson() は promise を返す
const response = await fetch(url); // fetch() が返した promise の処理が完了するのを待つ
const json = await response.json(); // json() が返した promise の処理が完了するのを待つ
return json;
};
//
const json = await getJson('https://example.com/get/something'); // getJson() が返した promise の処理が完了するのを待つ
console.log(json.something);
Deno ではコード全体が非同期になるため、上記の書き方がそのまま使用できます。
ブラウザの JavaScript では実行中のスクリプトがいつ終了するのか分からないと困るため、await
を使用しているコード全体を async
関数で囲う必要があります。
(async () => {
//
const getJson = async url => { // getJson() は promise を返す
const response = await fetch(url); // fetch() が返した promise の処理が完了するのを待つ
const json = await response.json(); // json() が返した promise の処理が完了するのを待つ
return json;
};
//
const json = await getJson('https://example.com/get/something'); // getJson() が返した promise の処理が完了するのを待つ
console.log(json.something);
})(); // promise を返しているが await していないため、いつ処理が終了するか分からないまま次に進んでいる
2. promise でないものを promise にしたいときに new Promise()
する
比較的新しい JavaScript の機能やライブラリでは初めから promise を返すようになっているため、ほぼ async
, await
だけで書けますが、古い JavaScript の機能や一部のライブラリでは「コールバック関数」を用いて非同期処理をしているため await
できません。
そこで new Promise()
を使用することで、コールバック関数による非同期処理を promise にすることができます。
const promise = new Promise((resolve, reject) => {
// ...
// 処理が終了したら resolve(返り値) を呼ぶ
// エラーが発生したら reject(エラー情報) を呼ぶ
});
// 画像読み込みを開始しているが、いつ画像が表示されるか分からない
const img = new Image();
img.onload = () => {
// ... 画像表示処理
};
img.onerror = e => {
// ... 読み込みエラー時の処理
};
img.src = 'https://example.com/something.png';
// ...
// ソースコードのここの時点で、画像が表示されているかどうかは分からない
//
const promise = new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => { resolve(img); };
img.onerror = e => { reject(e); };
img.src = 'https://example.com/something.png';
});
//
const img = await promise;
// ... 画像表示処理
// ソースコードのここの時点で、画像は表示されている
「なぜコールバック関数による非同期処理よりも promise による非同期処理が良いのか」はもうひとつの記事で説明していますので、ここでは「とにかく promise の方が良い」と思ってください。
参考「Promise, async, await がやっていること (Promise と async は書き換え可能?) - Qiita」(再掲)
3. new Promise()
は処理したいときにその都度 new
する
既に await
して完了した promise を再度 await
しても、その promise に割り当てられている処理は再実行されないため、promise は使いまわすことができません。
async
関数は呼ぶたびに新たな promsie が生成されますが、new Promise()
の場合は毎回 new
してください。
//
const sleepPromise = new Promise(resolve => {
setTimeout(resolve, 1000);
});
//
console.log('a');
await sleepPromise; // 1 秒待機
console.log('b');
await sleepPromise; // 1 秒待機してくれない
console.log('c');
//
const sleep = () => new Promise(resolve => {
setTimeout(resolve, 1000);
});
//
console.log('a');
await sleep(); // 1 秒待機
console.log('b');
await sleep(); // 1 秒待機
console.log('c');
4. promise.then()
は使わない
promise には then()
というメソッドがありますが、これを使いやすくしたものが await
なので、使わないでください。
「Promise チェーン」はもはや古い概念です (言い過ぎ?) 。
JavaScript が今でも prototype を内部的に使用しているにもかかわらずコードを書くときは class 構文を使用するように、then()
もあるけど使わないでください。
5. promise.catch()
は場合によっては使う
then()
と同様に catch()
というメソッドもあるのですが、こちらはコードのテクニックとして使用したほうが便利な場合があります。
非同期処理で発生したエラーをスルーして無視したい場合に使用すると良いです。
//
const getJson = async url => {
const response = await fetch(url);
const json = await response.json();
return json;
};
//
const json = await getJson('https://example.com/get/something').catch(() => ({ something: '(default)' }));
console.log(json.something);
少し応用的な使い方として、Promise.all()
でまとめて promise を並列実行するとき、一部の promise だけエラーを無視することも可能です。
const jsons = await Promise.all([
getJson('https://example.com/get/required1'),
getJson('https://example.com/get/required2'),
getJson('https://example.com/get/required3'),
getJson('https://example.com/get/optional1').catch(() => ({})),
getJson('https://example.com/get/optional2').catch(() => ({}))
]);