Help us understand the problem. What is going on with this article?

「async」「await」なんだこれ? ついでにforEachの性質について

Array.prototype.forEach() - JavaScript | MDN

forEach は同期関数を期待する

のコード例を見ると

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b)
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

関数の前にawaitという記述があったり、関数を定義する前にasyncが付いていて意味がわからなかった。

「async」とは

そもそも「async」とは「Async Function」を定義するためのキーワード

前提として、ES2017で非同期処理の関数を定義するための構文である「Async Function」が導入されていたらしい。Async Functionはpromiseインスタンスを返す関数を定義する構文。
(参考:非同期処理:コールバック/Promise/Async Function · JavaScript Primer)

「async」の使い方

使い方は関数を定義する前に付けるだけ。これだけでpromiseインスタンスを返す非同期処理の関数を定義することができるようになる。

async function testAsync() {
    return "Qiita";
}
// testAsync関数はPromiseを返す
testAsync().then(value => {
    console.log(value); // => "Qiita"
});

/*ちなみにasyncを外すと「TypeError: doAsync(...).then is not a function」。
Promiseインスタンスが返されてないから当然thenメソッドは使えなくなる*/

function testAsync() {
    return "Qiita";
}

testAsync().then(value => {
    console.log(value); // => "TypeError: doAsync(...).then is not a function"
});

「await」とは

point
1.promise処理が終わるまで待ってくれる
2.「async」をつけた関数の中で使う

メリット

非同期処理を同期処理のような見た目で書けるようになる。

「await」、「async」を実際に使ってみる

ここでは、引数で与えられたpathを最終的に配列にして出力するといった処理を行うよう実装する。

function dummyFetch(path) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (path.startsWith("/resource")) {//引数の文字列が"/resource"で始まるかで判定
        resolve({ body: `Response body of ${path}` });
      } else {
        reject(new Error("NOT FOUND"));
      }
    }, 1000 * Math.random())
  });
}

async function fetchAB() {
  const result = [];
  const responseA = await dummyFetch("/resource/A"); //待て!
  // 次の行はdummyFetchcの非同期処理が完了されるまで実行されない
  result.push(responseA.body);
  const responseB = await dummyFetch("/resource/B"); //待て!
  // 次の行はdummyFetchの非同期処理が完了されるまで実行されない
  result.push(responseB.body);
  return result;
}

//リソースを取得して出力する
fetchAB().then((results) => {
  console.log(results);
});

「forEach」の性質を再確認。

「await」、「async」の正体がなんとなく掴めたところで最初のコード例に戻り、
forEachの性質を再確認してみます。

let ratings = [5, 4, 5];
let sum = 0;

let sumFunction = async function (a, b) //Promiseを返す
{
  return a + b
}

ratings.forEach(async function(rating) {
  sum = await sumFunction(sum, rating)
  /*
    awaitの右辺にあるPromiseインスタンスの状態が変わる
    (≒sumFunctionの処理が終わる)のを、forEachは待ってくれない
  */
})

console.log(sum)
// 本来期待される出力: 14
// 実際の出力: 0

これは本来期待される出力結果は14だが、実際は0になってしまう。
原因はforEachはプロミス(非同期処理)を待ってくれない。せっかちなんですね〜。
今回の例で言うと、「sumFunction」を待ってくれない。

デバックして確認してみると、raitingの引数は「5→4→5」と最後まで渡されているが、足し算の処理は行われていない
だから結局、sumは初期値で設定した0のままになっている。

まだ完全に非同期処理の概念だったりが分かったようなわかんないような感じなので(多分全然分かってない)
今後の学習でさらに知識を増やしていきたい。

L_ryo
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした