【JavaScript】async/await で非同期処理 (async/await使ってPromiseを簡単にする)

async-await

こんにちは、フロントエンドエンジニアのまさにょんです。

今回は、JavaScriptでasync/await で非同期処理 (async/await使ってPromiseを簡単にする)方法について解説します。

async/await で非同期処理(Promise)をより簡単にする

JavaScriptの非同期処理には、欠かせない Object である Promise

そんな Promiseの記述をより簡単に使いやすくするためにできたのが、async/await です。

今回は、そんな async/await の使い方やポイントについてまとめています。

Promise については、以前の記事でまとめていますので、よかったら参照してみてください。

asyncとは、非同期関数 (async関数)を定義するためのKeyWord

async とは、非同期関数 (async関数)を定義するためのKeyWordです。

ポイントは、async を関数の宣言の前に付けると、その関数は実行結果(返り値)として必ず Promise を返すようになることです。

MDNの説明がわかりやすく、詳しいので引用します。

[ 非同期関数 ]

非同期関数は async キーワードで宣言され、その中で await キーワードを使うことができます。 

async および await キーワードを使用することで、

プロミスベースの非同期の動作を、プロミスチェーンを明示的に構成する必要なく、

よりすっきりとした方法で書くことができます。

[ 返り値 ]

Promise で、非同期関数から返される値で解決するか、

または非同期関数内で捕捉されなかった例外で拒否されます。

[ 解説 ]

非同期関数には、 await 式を置くことができます。

await 式は返されたプロミスが履行されるか拒否されるまで実行を中断することで、

プロミスを返す関数をあたかも同期しているかのように動作させます。

プロミスの解決済みの値は、await 式の返値として扱われます。

async と await を使用すると、非同期コードに通常の try / catch ブロックを使用することができます。

メモ: async/await の目的は、プロミスベースの API を利用するのに必要な構文を簡素化することです。 

非同期関数は常にプロミスを返します。

非同期関数の返値が明示的にプロミスでない場合は、暗黙的にプロミスでラップされます。

引用元: MDN: 非同期関数

上記の説明にもあるとおり、

async 文と、この後説明するawait 文の目的は、Promise ベースの API を利用するのに必要な構文をより簡単に記述できるようにすることです。

非同期関数 (async関数)は、実行結果(返り値)として常に Promise を返します。

また、非同期関数 (async関数)の返り値(returnの値)が明示的に Promise でない場合は、暗黙的に Promise でラップされます。

つまり、return new Promise((resolve, reject) => { ・・・} ) ではなく、

return '非同期-ロボ玉'; のように、Promiseではない、普通の返り値を設定した場合は、

その返り値をPromise でラップしてくれるのです。

このような Promiseで自動的にラップしてくれるような機能のおかげで、

Promiseを返すために、いちいち「 new Promise() 」する必要がなくなりCodeがより簡潔になります。

// 1. async で「非同期関数」を定義する => 必ずPromiseを返します。
const AsyncRobotama = async () => {
    return 'AsyncRobotama';
}

// 2. return new Promise((resolve, reject) => { ・・・} ) を省略できる!

// 3. 非同期関数 (async関数)の実行結果(返り値)は Promise なので、thenメソッドが使える!
AsyncRobotama()
    .then( result => {
        console.log(result);
    });

// [ 実行結果 ]
// AsyncRobotama
// Promise {<fulfilled>: undefined}
    // [[Prototype]]: Promise
    // [[PromiseState]] : "fulfilled"
    // [[PromiseResult]]: undefined

上記のSampleCodeでは、明示的に Promisereturn していませんが、自動で Promise にラップされた返り値になります。

もちろん、async関数で今まで通りの方法で Promise を実行結果(返り値)として返すこともできます。

// もちろん、async関数で今まで通りの方法でPromiseを返すこともできます。

const AsyncRobotama2 = async () => {
    return new Promise((resolve, reject) => {
      resolve('AsyncRobotama2');
    });
  }

AsyncRobotama2().then(result => {
    console.log(result);
});

// [ 実行結果 ]
// AsyncRobotama2
// Promise {<fulfilled>: undefined}
    // [[Prototype]]: Promise
    // [[PromiseState]] : "fulfilled"
    // [[PromiseResult]]: undefined

awaitとは、async関数からPromiseが返ってくるのを待つKeyWord

awaitとは、async関数からPromiseが返ってくるのを待つためのKeyWordであり、非同期処理の実行を同期的に扱うための機能です。

awaitは、Promiseオブジェクトを返す関数の実行中に使用され、Promiseが解決(resolve)されるまで、その関数の実行を一時停止し、Promiseが決定されるのを待ちます。

await を使用する際のポイントは、await は必ず、async関数の内部で使用することです。

async関数内でawaitを使うことで、非同期処理を同期処理のように扱えます。

async関数の中だけで省略Keywordであるawaitは使えるので「async/await」はセットで考えられるものなのです。

// 1. async で「非同期関数」を定義する => 必ずPromiseを返します。
const AsyncRobotama3 = async () => {
    return 'AsyncRobotama3';
}

const DevlopRobotama = async () => {

    // 2. await は必ず、async関数の内部で使います!
    const result = await AsyncRobotama3();
    console.log(result);
}

DevlopRobotama();

// [ 実行結果 ]
// AsyncRobotama3
// Promise {<fulfilled>: undefined}
    // [[Prototype]]: Promise
    // [[PromiseState]] : "fulfilled"
    // [[PromiseResult]]: undefined

こちらもMDNの説明がわかりやすく、詳しいので引用します。

[ await ]

await 演算子はプロミス (Promise) を待つために使用します。

通常の JavaScript コードで、 async function の内部でのみ使用することができます。

async functionによってPromise が返されるのを待機するために使用します。

[ 解説 ]

await 式は async 関数の実行を一時停止し、 

Promise が決定される(すなわち履行または拒否される)まで待ち、

履行された後に async 関数の実行を再開します。

最下位時に、 await 式の値は履行された Promise の値になります。

Promise が拒否された場合、await 式は拒否された値で例外を発生させます。

await 演算子に続くの値が Promise ではなかった場合、解決された Promise に変換されます。

[ Promiseの履行を待つ ]

Promise が await 式に渡された場合、 Promise が履行されて履行値を返すのを待ちます。

[ Promiseへの変換 ]

値が Promise でない場合は、値を解決済みの Promise に変換して待ちます。

[ Promiseの拒否 ]

Promise が拒否された場合、拒否された値で例外が発生します。

[ 拒否されたPromiseの処理 ]

拒否された Promise は try ブロックなしで処理することができます。

引用元: MDN: await

上記にあるように、awaitは、async functionによってPromise が返されるのを待機するために使用します。

Promise が履行か拒否かのどちらかの状態 (PromiseStatus)になったとき(Promiseの返却時)に、async関数の実行を再開します。

awaitthen() を続けて値を取り出す Promiseチェーンを省略した記法です。

つまり、Promise.then( result => { ... } ) を省略したものということです。

次のSampleCodeを見ると、await の効果がわかりやすいと思います。

// 1. 非同期処理をする関数
const PromiseCalc = (num) => {
    return new Promise((resolve) => {

        setTimeout(()=> { resolve(num * num) }, 2000);
    });
};


// 2. 従来のthen() メソッドチェーン
PromiseCalc(10)
.then((data)=> {
 
    console.log(data);
    return PromiseCalc(100);
})
.then((data)=> {
 
    console.log(data);
    return PromiseCalc(1000);
})
.then((data)=> {
    console.log(data);
});


// 3. async/await による thenメソッドチェーンの省略
const AsyncAll = async () => {
  
    console.log(await PromiseCalc(10));
    console.log(await PromiseCalc(100));
    console.log(await PromiseCalc(1000));
}
 
AsyncAll();


// [ 実行結果 ]
// Promise {<fulfilled>: undefined}
    // [[Prototype]]: Promise
    // [[PromiseState]] : "fulfilled"
    // [[PromiseResult]]: undefined
// 100
// 100
// 10000
// 10000
// 1000000
// 1000000

複数のPromiseを並列で走らせるときに、awaitは使いやすいです。

await を使わないと then() でデータを取り出してネストが深くなり、処理が複雑になりがちです。

// 複数のPromiseを並列で走らせるときに、awaitは使いやすい

// 1. 非同期処理をする関数
const LateSeconds = (x) => {
    return new Promise(resolve => {
        setTimeout(() => {
        resolve(x);
        }, x * 1000);
    });
}

// 2-1. async関数 Ver.1
const LateCompany = async () => {
    let a = LateSeconds(1); // ここで1秒待つ
    let b = LateSeconds(2); // ここで2秒待つ
    return `${await a + await b}秒会社に遅刻しました!`; // 1秒待つのと2秒待つのが並行で行われる
}

// 2-2. then() でも取り出せます
LateCompany().then(v => {
    console.log(v);
});

// [ 実行結果 ]
// 3秒会社に遅刻しました!


// 3-1. async関数 Ver.2
const LateCompany2 = async (x) => {
    let a = await LateSeconds(2); // ここで2秒待つ
    let b = await LateSeconds(3); // さらに3秒待つ
    return `${await a + await b}秒会社に遅刻しました!`; // 2秒待つのと3秒待つのが並行で行われる
}

// 3-2. await でも取り出せます
console.log(await LateCompany2());

// [ 実行結果 ]
// 5秒会社に遅刻しました!

Promise.all() と async/await を使った並列処理の比較

Promise.all() と async/await を使った並列処理の比較をしてみましょう。

// 1. 非同期処理をする関数
const PromiseCalc = (num) => {
    return new Promise((resolve) => {

        setTimeout(()=> { resolve(num * num) }, 2000);
    });
};

// [ Promise.all() と async/await を使った並列処理の比較 ]

// 2. Promise.all() を使った並列処理

// 2-1. Promise.all() では、PromiseCalc() の引数違いを3つ同時に実行しています。
Promise.all([
    PromiseCalc(10),
    PromiseCalc(100),
    PromiseCalc(1000)
])
.then((data) => {
    // 2-2. 最後に「then」で実行結果を配列として出力します。
    console.log(data);
});

// [ 実行結果 ]
// (3) [100, 10000, 1000000]


// 3. async/await を使った並列処理
const AsyncAllFunc = async () => {
    
    // 3-1. まず最初に、実行予定のPromise処理をすべて起動させて変数に格納します。
    const promise1 = PromiseCalc(10);
    const promise2 = PromiseCalc(100);
    const promise3 = PromiseCalc(1000);
 
    // 3-2.「await」を付与することですべてのPromise処理を並列に動かして結果を取得できる。
    console.log([await promise1, await promise2, await promise3 ]);
};
 
AsyncAllFunc();

// [ 実行結果 ]
// (3) [100, 10000, 1000000]

関連記事

JavaScript書籍 Ver. 中級-上級者向け

JavaScript書籍 Ver. 初級者向け

Twitterやってます!Follow Me!

神聖グンマー帝国の逆襲🔥

神聖グンマー帝国の科学は、世界一ぃぃぃぃぃぃ!!!!!

参考・引用

  1. async await の使い方
  2. 【JavaScript入門】5分で理解!async / awaitの使い方と非同期処理の書き方
  3. MDN: 非同期関数
  4. MDN: await

最近の投稿