🙌

setTimeoutじゃできないやつをasync/awaitで実現する

2023/07/31に公開

setTimeoutで実現したいと思っても出来ないことがあります。
非同期はpromiseのthenで書いたことはあったのですが、苦手意識がありました

今回はasync/awaitで書き換えてみました

参考記事

https://rightcode.co.jp/blog/information-technology/javascript-async-await

setTimeoutだと意図した順番にならない時がある

例えば「初回」→3秒後「2回目」→1秒後「3回目」と出力したいとき、setTimeoutを使うと思った結果にならないことがあります

const message = text => console.log(text);

function messFunc() {
  message("初回");
  setTimeout(() => { message("2回目")}, 3000);
  setTimeout(() => { message("3回目")}, 1000);
}

messFunc();
//=> 初回 3回目 2回目

上から順にsetTimeoutが実行され、単に指定時間経過後に処理が実行されるからです

setTimeoutで順番通りに実行するためには、以下のように前の処理の時間を考慮し、考えて書き換えなくてはなりません

const message = text => console.log(text);

function messFunc() {
  message("初回");
  setTimeout(() => { message("2回目")}, 3000);
  setTimeout(() => { message("3回目")}, 4000);
}

messFunc();
//=> 初回 2回目 3回目

これが例えば「3回目を初回の2秒後に、2回目を3回目の4秒後」のように変更したいとなったときは、また処理の時間を考慮しながら書き換えなくてはなりません
処理が複数になればなるほど煩雑になり、正直面倒ですし可読性も良くないです

async/awaitを使う

これを解決するために、Promiseとasync/awaitで非同期関数を同期的に扱い、特定の処理が終わってから次の処理を実行する関数を記述します

const promiseFunc = (func, time) => {
  return new Promise(resolve => {
    setTimeout(() => {
      func();    
      resolve();
    }, time);
  });
}

const a = () => console.log("a");
const b = () => console.log("b");
const c = () => console.log("c");

async function loading() {
  promiseFunc(a, 0);
  await promiseFunc(b, 3000);
  await promiseFunc(c, 1000);
}

loading();
//=> a b c

任意の関数をn秒後に実行した後、resolveを返すpromiseFunc関数を設定します

await指定された関数は前の処理がresolveを返してから実行します
そのためawait指定をすることで、「a」→3秒後「b」→1秒後「c」のように想定どおりにコンソールに表示されます

何の処理の後にどのような処理を走らせるか、見通しが良くなります

resolveの何たるかは参考サイトがわかりやすいです

複数の処理を非同期で同時に実行する

以下のようにPromise.allを使うと、複数の処理を非同期で同時に実行できます

const promiseFunc = (func, time) => {
  return new Promise(resolve => {
    setTimeout(() => {
      func();    
      resolve();
    }, time);
  });
}

const a = () => console.log("a");
const b = () => console.log("b");
const c = () => console.log("c");
const d = () => console.log("同時です-1");
const f = () => console.log("同時です-2");

async function loading() {
  promiseFunc(a, 0);
  await promiseFunc(b, 3000);
  await promiseFunc(c, 1000);
  await promiseFunc(() => {
    Promise.all([
      promiseFunc(d, 1000),
      promiseFunc(f, 1000)  
    ]);
  }, 500);
}

loading();
//=> a b c 同時です-1 同時です-2

また引数の関数は無名関数でも構いません

await promiseFunc(() => console.log("無名関数です"), 3000);

非同期で複数処理が終わってから次の処理を行う例

2つの非同期関数を待ってから次の処理を行う場合の例です

const promiseFunc = (func, time) => {
  return new Promise(resolve => {
    setTimeout(() => {
      func();    
      resolve();
    }, time);
  });
}

const a = () => console.log("a");
const b = () => console.log("b");
const c = () => console.log("c");

async function loading() {
  Promise.all([
    promiseFunc(a, 1000),
    promiseFunc(b, 3000)
  ]);
  await promiseFunc(c, 1000);
}

loading();
//=> a b c

2つのpromiseFuncが同時に実行され、そこから1秒後にaの処理、3秒後にbの処理が行われ、すべての処理が完了し1秒後にcが実行されます

最後に

async/awaitは僕も何故か苦手意識がありましたが、上手く使えれば色々便利です

例えばローディングアニメーションが終わってからファーストビューのスライダーを開始させるとか、スタイルの付与処理を終えたら次のスタイルを付与するなど
前の処理を待ちたい時に良さそう

最初は従来のPromise(then)に触れてからの方がいいかもしれません

Discussion