LoginSignup
26
21

More than 1 year has passed since last update.

もっと簡単に async, await, Promise

Last updated at Posted at 2020-07-25

以前も 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 にすることができます。

new 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';

// ...
// ソースコードのここの時点で、画像が表示されているかどうかは分からない
new Promise() で promise にする
// 
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 だけエラーを無視することも可能です。

Promise.all() で一部の promsie だけエラーを無視する
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(() => ({}))
]);
26
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
21