28
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

もっと簡単に 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 data = response.jsonSync();
    return data;
};

// 
const data = getJsonSync('https://example.com/get/something');

console.log(data.something);
実際の書き方
// 
const getJson = async url => { // getJson() は promise を返す
    const response = await fetch(url); // fetch() が返した promise の処理が完了するのを待つ
    const data = await response.json(); // json() が返した promise の処理が完了するのを待つ
    return data;
};

// 
const data = await getJson('https://example.com/get/something'); // getJson() が返した promise の処理が完了するのを待つ

console.log(data.something);

Deno ではコード全体が非同期になるため、上記の書き方がそのまま使用できます。

WEB ブラウザの JavaScript では実行中のスクリプトがいつ終了するのか分からないと困るため、await を使用しているコード全体を async 関数で囲う必要があります。

実際の書き方 (WEB ブラウザの場合)
(async () => {

    // 
    const getJson = async url => { // getJson() は promise を返す
        const response = await fetch(url); // fetch() が返した promise の処理が完了するのを待つ
        const data = await response.json(); // json() が返した promise の処理が完了するのを待つ
        return data;
    };

    // 
    const data = await getJson('https://example.com/get/something'); // getJson() が返した promise の処理が完了するのを待つ

    console.log(data.something);

})(); // promise を返しているが await していないため、いつ処理が終了するか分からないまま次に進んでいる

2. promise でないものを promise にしたいときに new Promise() する

2.1. 基本

比較的新しい JavaScript の機能やライブラリでは初めから promise を返すようになっているため、ほぼ async, await だけで書けますが、古い JavaScript の機能や一部のライブラリでは「コールバック関数」を用いて非同期処理をしているため await できません。

そこで new Promise() を使用することで、コールバック関数による非同期処理を promise にすることができます。

new Promise() の書き方
const promise = new Promise((resolve, reject) => {
    // ...
    // 処理が終了したら resolve(返り値) を呼ぶ
    // エラーが発生したら reject(エラー情報) を呼ぶ
});

2.2. 例: 画像読み込み

以前は以下のようにして画像の読み込みが行われていました。

コールバック関数を用いる場合
// 画像読み込みを開始しているが、いつ画像が表示されるか分からない
const image = new Image();
image.onload = () => {
    // ... 画像表示処理
};
image.onerror = event => {
    // ... 読み込みエラー時の処理
};
image.src = 'image.png';

// ...
// ソースコードのここの時点で、画像が表示されているかどうかは分からない
new Promise() を用いる場合
// 
const promise = new Promise((resolve, reject) => {
    const image = new Image();
    image.onload = () => resolve(image);
    image.onerror = event => reject(event);
    image.src = 'image.png';
});

// 
const image = await promise;
// ... 画像表示処理
// ソースコードのここの時点で、画像は表示されている

現在は new Promise() を書かずに promise として扱うことが出来ます。

const image = new Image();
image.src = 'image.png';
await image.decode();
// ... 画像表示処理
// ソースコードのここの時点で、画像は表示されている

参考「最新の JavaScript で Image を読み込む方法 - Qiita

3. new Promise() は処理したいときにその都度 new する

既に await して完了した promise を再度 await しても、その promise に割り当てられている処理は再実行されないため、promise は使いまわすことができません。

async 関数は呼ぶたびに新たな promise が生成されますが、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 構文を使用するのと同様です。

5. promise.catch() は場合によっては使う

then() と同様に catch() というメソッドもありますが、こちらはコードのテクニックとして使用した方が便利な場合があります。

非同期処理で発生したエラーをスルーして無視したい場合に使用すると良いです。

エラーを無視する
//
const getJson = async url => {
    const response = await fetch(url);
    const data = await response.json();
    return data;
};

//
const data = await getJson('https://example.com/get/something').catch(() => ({ something: '(default)' }));

console.log(data.something);

少し応用的な使い方として、Promise.all() でまとめて promise を並列実行するとき、一部の promise だけエラーを無視することも可能です。

Promise.all() で一部の promise だけエラーを無視する
const data = 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(() => null),
    getJson('https://example.com/get/optional2').catch(() => null)
]);
28
22
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
28
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?