非同期処理(Promise, async/await)

ポーリング

フロント側からサーバーに対して一定間隔にhttpリクエストを投げる処理。 サーバから新しい情報を取得する目的で利用する。

Promsie

非同期処理を行う最も代表的なオブジェクト。 PromiseにはPromiseStatusという状態があって3種類ある。

  • pending - 未解決(処理完了まで待機中)
  • resolved - 解決済み(処理が無事に終わった)
  • rejected - 拒否(処理が失敗に終わった)

New Promise()でPromiseオブジェクトが生成されると、PromiseStatusはpending(待機中)になる。

処理が成功したらPromiseStatusはresolved(解決済み)に変わり、thenに書かれた処理が実行される。

失敗したらPromiseStatusはrejected(失敗)に変わり、catchに書かれた処理が実行される。


Promiseの書き方
const promise = new Promise((resolve, reject) => {})

引数には関数を渡し、その関数の第一引数にresolveを設定し、第二引数にrejectを任意で設定する。resolve、rejectともに関数である。


resolve

resolveさせてみると、

// rejectは今回使わない
const promise = new Promise((resolve) => {
  resolve();
}).then(() => {
  console.log("resolve done!");
});

この時、Promiseオブジェクトが生成される=>resolveメソッド呼び出しで状態がresolvedに変わる=>resolved(解決済み)なのでthenの処理が実行される("resolve done!"が表示される)、という流れで処理が行われる。


また、resolveメソッドは引数を受け取ることもできる。

const promise = new Promise((resolve) => {
  resolve("parameter is passed!!");
}).then((value) => {
  console.log(value);
});

実行すると、「"parameter is passed!!"」が表示される。resolveメソッドに渡した「"parameter is passed!!"」がthenの第一引数valueに渡されていることがわかる。resolveメソッドに渡した「"parameter is passed!!"」はPromiseValueというものに格納されており、次に呼ばれるメソッドの第一引数に渡すことができる。


reject

次にrejectすると、

const promise = new Promise((resolve, reject) => {
  reject();
}).then(() => {
  console.log("resolve done!");
}).catch(() => {
  console.log("reject done!");
});

thenの処理は実行されることなく、catchの処理が実行されて「"reject done!"」と出る。
この時、Promiseオブジェクトが生成される=>rejectメソッド呼び出しで状態がrejectedに変わる=>catchに飛ぶ=>"rejected done!"が表示される、という流れで処理が行われる。

ちなみにcatchで処理実行された後、「処理が正常に実行された」ということで状態はresolveになっている。注意。


Promise.all

Promise.all()は配列でPromiseオブジェクトを渡し、全てのPromiseオブジェクトがresolvedになったら、次の処理に進む。

const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("promise1 done!!");
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("promise2 done!!");
});

Promise.all([promise1, promise2]).then(() => {
  console.log("All promises done!!");
}).catch((err) => {
  console.log(err);
});;

実行結果は以下の通り。

promise1 done!!
promise2 done!!
All promises done!!


Promise.allは配列を一行ごとにPromiseで処理しなければならない時に有効である。foreachで実装しようとしても、エラーが出てしまう。foreachはコールバックの返り値を無視するからである。そこで代わりにPromise.allを使う。1つでも処理が失敗すれば、全てが失敗となる。


Promise.race

Promise.race()は配列でPromiseオブジェクトを渡し、どれか1つのPromiseオブジェクトがresolvedになったら、次の処理に進む。

const promise1 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 1000);
}).then(() => {
  console.log("promise1 done!!");
});

const promise2 = new Promise((resolve) => {
  setTimeout(() => {
    resolve();
  }, 3000);
}).then(() => {
  console.log("promise2 done!!");
});

Promise.race([promise1, promise2]).then(() => {
  console.log("One promise done!!");
});

実行結果は以下の通り。

promise1 done!!
One promise done!!
promsie2 done!!


async/await

Promiseは便利だが、インデントが深くなりがち。1つの非同期処理を行うメソッドには不向き。そこで、await/asyncによって同期処理のように書くことができる。

async

asyncは非同期関数を定義する関数宣言であり、関数につけることでPromiseオブジェクトを返す関数になる。

const printLog = async () => {
   return 1
};

printLog()が値を返した時、Promiseは戻り値をresolveし、戻り値はPromiseValueとして扱われる。

await

awaitはPromiseオブジェクトが値を返すのを待っている演算子。必ずasyncなメソッドの中で使うこと。

const asyncFunc = async () => {
  let x, y;

  x = new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  }).then((val) => {
    console.log(val);
  });

  y = new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  }).then((val) => {
    console.log(val);
});

  console.log(x + y)
}

これはawaitを使うと、よりシンプルになる。

const asyncFunc = async () => {
  let x, y;

  x = await new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  });

  y = await new Promise(resolve => {
    setTimeout(() => {
      resolve(1);
    }, 1000 )
  });

  console.log(x + y)
}

実行結果

2

awaitがあることで、resolved(解決)した後に実行されるthenがなくなった。

以上のことから、Promiseオブジェクトを受け取りにはawaitが必要である。awaitが必要ということは、その処理はasyncなメソッドの中にある必要があるということを認識しておくこと。