【JS】【TS】非同期処理 ~ async/await, Promise ~

■ はじめに

https://dk521123.hatenablog.com/entry/2021/01/14/101645

で、外部APIにアクセスしてデータ取得することを行った。
その際、非同期処理になる。

そこで、JavaScript/TypeScriptでの非同期処理を学ぶ。

目次

【1】関連用語
 1)async
 2)await
 3)Promise
【2】サンプル
 例1:await で待たされるダメな実装
 例2:効率的な作業をした実装 - Promise.all()
 例3:例2のVue+TypeScript版

【1】関連用語

1)async

* 以下のサイトを一読しておくといい。(物凄く分かりやすい)

https://qiita.com/7tsuno/items/6d5a27ffe9143b35defe

* async(Asynchronous) = 非同期
* 対象関数が非同期であることを宣言する

async(JavaScript

async function demoAsynchronous() {
    return 'Hello world!!';
}

2)await

* 非同期処理を待つ
 => Promiseの結果が出るまで待つ

await(JavaScript

async function demoAsynchronous2() {
    const result = await demoPromise('Mike');
    console.log(result);
}

3)Promise

* 非同期処理で完了した際のレスポンスを表すオブジェクト

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise

* Promise の説明が以下が分かりやすい。

https://medium.com/acompany/javascript%E3%81%AEasync-await%E3%82%92%E5%AE%8C%E5%85%A8%E3%81%AB%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B-6552337eb92

* 以下の関連記事で行ったFutureオブジェクトと同じ
 => 『処理の実行担当者は、処理が渡されると別スレッド上で処理を開始して、
      メインスレッドには即座にFutureオブジェクトを返すこと』

https://dk521123.hatenablog.com/entry/2014/01/18/000804

Promise(JavaScript

function demoPromise(name) {
  // 引数「resolve」に取得したい値を設定することで
  // 非同期処理の結果を格納することができる
  return new Promise(function(resolve) {
    resolve(`Hi, ${name}`);
  });
}

Promise 状態

1)Fulfilled ... 成功
2)Rejected ... 失敗
3)Pending ... ペンディング中

Promiseの詳細は、以下のサイトを参照。

https://qiita.com/sotszk/items/f23199e864cba47455ce

【2】サンプル

https://qiita.com/7tsuno/items/6d5a27ffe9143b35defe

の説明が分かりやす過ぎたので、理解を深めるために
自分でも色々と弄れるサンプルを作ってみた。
(f12なので、ログを出しながら動かせば理解しやすいと思う)

例1:await で待たされるダメな実装

<!DOCTYPE html>
<html lang="jp">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello World!</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- ★HTML部分★ -->
<div id="app-x">
  <div><button v-on:click="onClickEvent">Click me!</button></div>
  <div><p>{{ message }}</p></div>
</div>
<script>
<!-- ★JS部分★ -->
var dummyWork = (process, requiredTime) => {
  var waittingTime = requiredTime * 1000;
  console.log("Start work[" + process + "] - " + waittingTime);

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Done");
      console.log("Done - " + process);
    }, waittingTime);
  });
};

async function cook() {
  // 麺担当に麺をゆでてもらう
  const noodles = await dummyWork('noodle', 3);

  // スープ担当にスープを作ってもらう
  const soup = await dummyWork('soup', 2);

  // 具担当にチャーシューを切ってもらう
  const pork = await dummyWork('pork', 1);

  // ラーメンを完成させる。
  const ramen = await dummyWork('ramen', 1);
  
  return "Done!!";
};

var app = new Vue({
  el: '#app-x',
  data: {
    message: 'Click the button',
  },
  methods: {
    onClickEvent: function () {
      const startTime = performance.now();
      this.message = "Start work!";
      cook().then(value => {
        console.log(value); // => Done!!
        this.message = value;

        const endTime = performance.now();
        console.log("Time : " + (endTime - startTime));
      });
    }
  }
});
</script>
</body>
</html>

出力結果

Start work[noodle] - 3000
Done - noodle
Start work[soup] - 2000
Done - soup
Start work[pork] - 1000
Done - pork
Start work[ramen] - 1000
Done - ramen
Done!!
Time : 7037.229999999909

例2:効率的な作業をした実装 - Promise.all()

<!DOCTYPE html>
<html lang="jp">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hello World!</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<!-- ★HTML部分★ -->
<div id="app-x">
  <div><button v-on:click="onClickEvent">Click me!</button></div>
  <div><p>{{ message }}</p></div>
</div>
<script>
<!-- ★JS部分★ -->
var dummyWork = (process, requiredTime) => {
  var waittingTime = requiredTime * 1000;
  console.log("Start work[" + process + "] - " + waittingTime);

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve("Done");
      console.log("Done - " + process);
    }, waittingTime);
  });
};

async function cook() {
  // 麺・スープ・チャーシューを一気に依頼
  const processes = [
    { name: "noodles", requiredTime: 3 },
    { name: "soup", requiredTime: 2 },
    { name: "pork", requiredTime: 1 }
  ];
  const [noodles, soup, pork] = await Promise.all(
    processes.map(process => dummyWork(process.name, process.requiredTime)));

  // ラーメンを完成させる。
  const ramen = await dummyWork('ramen', 1);
  
  return "Done!!";
};

var app = new Vue({
  el: '#app-x',
  data: {
    message: 'Click the button',
  },
  methods: {
    onClickEvent: function () {
      const startTime = performance.now();
      this.message = "Start work!";
      cook().then(value => {
        console.log(value); // => Done!!
        this.message = value;

        const endTime = performance.now();
        console.log("Time : " + (endTime - startTime));
      });
    }
  }
});
</script>
</body>
</html>

出力結果

Start work[noodles] - 3000
Start work[soup] - 2000
Start work[pork] - 1000
Done - pork
Done - soup
Done - noodles
Start work[ramen] - 1000
Done - ramen
Done!!
Time : 4027.9150000005757

例3:例2のVue+TypeScript版

<template>
  <div id="app-x">
    <div><button v-on:click="onClickEvent">Click me!</button></div>
    <div><p>{{ message }}</p></div>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";

@Component
export default class Home extends Vue {
  public message?: string = "Click here";
  
  private onClickEvent() {
    const startTime = performance.now();
    this.message = "Start work!";

    this.cook().then(value => {
      console.log(value); // => Done!!
      this.message = value;

      const endTime = performance.now();
      console.log("Time : " + (endTime - startTime));
    });
  }

  private async cook() {
    // 麺・スープ・チャーシューを一気に依頼
    const processes = [
      { name: "noodles", requiredTime: 3 },
      { name: "soup", requiredTime: 2 },
      { name: "pork", requiredTime: 1 }
    ];
    const [noodles, soup, pork] = await Promise.all(
      processes.map(process => this.dummyWork(
        process.name, process.requiredTime)));

    // ラーメンを完成させる。
    const ramen = await this.dummyWork('ramen', 1);
    
    return "Done!!";
  }

  private dummyWork(process: string, requiredTime: number) {
    const waittingTime = requiredTime * 1000;
    console.log("Start work[" + process + "] - " + waittingTime);

    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("Done");
        console.log("Done - " + process);
      }, waittingTime);
    });
  }
}
</script>

関連記事

TypeScript ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/21/180904
axios ~ 外部API通信ライブラリ ~
https://dk521123.hatenablog.com/entry/2021/01/14/101645
Jestを使った Vue / TypeScript の単体試験 ~ 非同期編 ~
https://dk521123.hatenablog.com/entry/2021/03/13/000000
Future パターン
https://dk521123.hatenablog.com/entry/2014/01/18/000804
タイマー処理 ~ setTimeout / setInterval etc ~
https://dk521123.hatenablog.com/entry/2021/02/01/000000
処理時間計測 ~ performance.now() ~
https://dk521123.hatenablog.com/entry/2021/02/05/000000
非同期に関する用語
https://dk521123.hatenablog.com/entry/2017/08/27/230218