Javascriptの非同期処理「async/await」をしっかり理解!

今回は、

  • 非同期処理ってそもそもなに?
  • どんなときに使えばいい?
  • awaitの使い方は待っているの?
  • なぜPromiseではなくてasync/awaitがいいの?

こんな悩みを解決するために

非同期処理(async/wait)とはなんぞやをわかりやすく紹介していきます。

まさ

非同期処理はasyncで書いて、awaitで受け取るんだよな(?)

後輩

先輩、非同期処理詳しくおしえてー

まさ

いいよー。ま、また今度時間作るね
(曖昧な理解しかしてないんだよな、
…教えるにはしっかり理解しないと)

僕も少し前までは、こんな状況でした。

今回人に教える機会があり、しっかり理解する必要があったため、

自分なりに学習し直してまとめました。

同じ悩みを持っている人の助けになればと思ったので、記事に残します。

目次

非同期処理(async/await)とは

基本的に全ての命名には意味があることが多いので、由来から調べます。

asyncAsynchronous(エイシンクロノス)=非同期の略語です。

awaitasync wait(非同期を待つ)の略語です。

さて、由来はわかりましたが、そもそも「非同期」がイメージわかないですよね。

いつもの書いているプログラムを想像してください。

基本的には上から下に向かって処理が行われると思います。

上から1行ずつ順番に行われていくのが同期処理=順次処理です。

例えばプログラムが膨大な量のデータを読み込んでそのデータを処理して画面描画するとします。

同期処理の場合どの様な動きになるでしょうか。

答えは、「データの読み込みが完了するまで、画面には何も描画されず、データ処理が完了したら画面描画されるです。

非同期処理を使用すると、

画面の枠組みだけ先に表示させておいて、
データ部分は読み込みが終わったときに表示させることができます。

この投稿を例とするのであれば、

①ナビゲーションやフッターメニューなど軽い描画処理
②本文(文章や画像)などデータベースに取得しにいく重い描画処理

を分割して、後者の重い処理はバックグラウンドで行って、準備でき次第処理するよ。

というイメージで、まっさらなローディング画面をサイト訪問者に対し表示するのではなく、

一時的なレスポンスとしてヘッダーやフッターの表示だけを行い、

時差で本文を表示させていくことができます。

待機ストレス軽減や離脱防止に効果があったりします。

実際の開発現場などでは

  • 外部通信
  • データ取得

に非同期処理が使われていることが多いです。

asyncの役割

まずはこのコードの返り値を見てみましょう。

async function testAsync() {
    return "here is sample";
}

const result = testAsync();
console.log(result);

返り値は

Promise { ‘here is sample’ }

とPromiseを返しています。

そうです。「パッと!ピッと!プロミス」です。

このPromise(約束)というのは非同期処理の操作が完了したときに返される値です。

今回のようにasync関数で直接returnすると、

値がPromise.resolve()でPromiseオブジェクトとしてカプセル化されます。

async関数の返り値はカプセル化されたPromiseオブジェクトとわかりました。

Promiseで書いたプログラムです。

const testPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("here is sample");
    }, 300);
});

console.log(testPromise);
testPromise.then((value) => console.log(value));

Asyncの出力結果とPromiseの出力結果と比べてみると、特に違いが見受けられません。

awaitの役割

asyncは従来のpromiseとほぼ同等とわかったところで、awaitに注目していきます。

await 式は async 関数の実行を一時停止し、 Promise が決定される(すなわち履行または拒否される)まで待ち、履行された後に async 関数の実行を再開します。最下位時に、 await 式の値は履行された Promise の値になります。

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

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

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/await#%E8%A7%A3%E8%AA%AC

公式は安定の非常に分かりづらい書き方。

asyncの役割で紹介したようにasyncが返すのはPromiseオブジェクト。

すなわち、ここではawaitでasync関数の結果を待てると理解できます。

ただし、awaitはasync関数の結果だけを待っているわけではありません。

引用文の最後の1行に「Promiseではなかった場合」と書かれてあるのです。

ようするに、awaitはawait演算子に続く式の結果を制限なく待っているのです。

なので、以下のような同期・非同期関数ともに受け取ることがきます。

function normalFunc() {
    return "普通の関数です";
}

async function testAsync() {
    return Promise.resolve("非同期関数です");
}

async function test() {
    const v1 = await normalFunc();
    const v2 = await testAsync();
    console.log(v1, v2);
}

test();

awaitの動きが少し見えてきたところで

  • awaitでPromiseオブジェクト以外の値を受け取った場合
  • awaitでPromiseオブジェクトを受け取った場合

の動きの違いを見てみましょう。

Promiseオブジェクトではない場合は、awaitの演算結果=受け取った値となります。

簡単な代入式みたいなイメージですね。

Promiseオブジェクトの場合は、後続の処理を待機させ、Promiseがresolveされます。
awaitの演算結果=resolveされた値となります。

非同期処理を「promise/then」と「async/await」で書いて比較してみる

setTimeoutを使った2秒待つ非同期処理を例に書いていきます。

function waitTwoSecond() {
    return new Promise(resolve => {
        setTimeout(() => resolve("2秒待ちました"), 2000);
    });
}
// Promise・then
waitTwoSecond().then(v => {
    console.log(v);
});

// async・await
async function testAsyncFunc() {
    const v = await waitTwoSecond();
    console.log(v);
}
testAsyncFunc();

結果は同じですね。(厳密には違うオブジェクトです)

ここまで見てみると、async/awaitがこんなに利用されている意味がわからないですね。

そうなのです。Promise連鎖単体だとそのメリットは見えてきません。

ではこちらの処理を実行していく処理を書いてみます。

function waitFewSec(ms) {
    return new Promise(resolve => {
        setTimeout(() => resolve(ms + 2000), ms);
    });
}

function stepOne(ms) {
    console.log(`stepOne ${ms}ms待ちます`);
    return waitFewSec(ms);
}

function stepTwo(ms) {
    console.log(`stepTwo ${ms}ms待ちます`);
    return waitFewSec(ms);
}

function stepThree(ms) {
    console.log(`stepThree ${ms}ms待ちます`);
    return waitFewSec(ms);
}

まずはPromise/Then

function doAllStep() {
    console.time("promiseThenの処理時間");
    stepOne(500)
        .then(time => stepTwo(time))
        .then(time2 => stepThree(time2))
        .then(result => {
            console.timeEnd("promiseThenの処理時間");
        });
}
doAllStep();

続いてasync/await

async function doAllStep() {
    console.time("asyncAwaitの処理時間");
    const time = await stepOne(500);
    const time2 = await stepTwo(time);
    const result = await stepThree(time2);
    console.timeEnd("asyncAwaitの処理時間");
}

doAllStep();

同じ処理をしていますが、async/awaitのほうがスッキリ簡潔にかけていますよね。

各stepの処理の引数が増えた場合を考えてみてください。

promise/thenの記述方法だとかなり恐ろしくなってきます。

さいごに

Javascriptのasync/awaitについて理解を深めてきました。

言葉で説明を受けて全てを理解するのは難しいと思います。

なので、記事を読み終わったら一度サンプルコードを動かして理解を更に深めることをおすすめします。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次