web-dev-qa-db-ja.com

async / awaitのタイムアウト

Node.jsとTypeScriptを使用しており、async/awaitを使用しています。これは私のテストケースです:

async function doSomethingInSeries() {
    const res1 = await callApi();
    const res2 = await persistInDB(res1);
    const res3 = await doHeavyComputation(res1);
    return 'simle';
}

機能全体のタイムアウトを設定したい。つまりres1が2秒、res2が0.5秒、res3が5秒かかる場合、3秒後にエラーをスローできるようにタイムアウトが必要です。

通常のsetTimeout呼び出しでは、スコープが失われるため問題になります。

async function doSomethingInSeries() {
    const timerId = setTimeout(function() {
        throw new Error('timeout');
    });

    const res1 = await callApi();
    const res2 = await persistInDB(res1);
    const res3 = await doHeavyComputation(res1);

    clearTimeout(timerId);

    return 'simle';
}

そして、私は通常のPromise.catchでそれをキャッチすることはできません:

doSomethingInSeries().catch(function(err) {
    // errors in res1, res2, res3 will be catched here
    // but the setTimeout thing is not!!
});

解決方法に関するアイデアはありますか?

21
nkint

Promise.race タイムアウトする:

Promise.race([
    doSomethingInSeries(),
    new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 11.5e3))
]).catch(function(err) {
    // errors in res1, res2, res3 and the timeout will be caught here
})

setTimeoutは、promiseでラップせずに使用できません。

35
Bergi

OK、私はこの方法を見つけました:

async function _doSomethingInSeries() {
    const res1 = await callApi();
    const res2 = await persistInDB(res1);
    const res3 = await doHeavyComputation(res1);
    return 'simle';
}

async function doSomethingInSeries(): Promise<any> {
  let timeoutId;

  const delay = new Promise(function(resolve, reject){
    timeoutId = setTimeout(function(){
      reject(new Error('timeout'));
    }, 1000);
  });

  // overall timeout
  return Promise.race([delay, _doSomethingInSeries()])
    .then( (res) => {
      clearTimeout(timeoutId);
      return res;
    });

}

誰かエラー?

私にとって少し臭いのすることは、Promisesを非同期戦略として使用すると、他の戦略が必要とするオブジェクトをあまりにも多く割り当てることになりますが、これはトピック外です。

4
nkint

@Bergiでの問題は、すでに約束を拒否した場合でもdoSomethingInSeriesが実行を継続するというものです。タイムアウトするだけでキャンセルする方がずっと良いです。

キャンセルのサポートは次のとおりです。

async function doSomethingInSeries(cancellationToken) {
  cancellationToken.throwIfCancelled();
  const res1 = await callApi();
  cancellationToken.throwIfCancelled();
  const res2 = await persistInDB(res1);
  cancellationToken.throwIfCancelled();
  const res3 = await doHeavyComputation(res1);
  cancellationToken.throwIfCancelled();
  return 'simle';
}

ただし、キャンセルトークンを各非同期関数に渡してそこで使用することをお勧めします。

キャンセルの実装は次のとおりです。

let cancellationToken = {
  cancelled: false,
  cancel: function() {
    this.cancelled = true;
  },
  throwIfCancelled: function() {
    if (this.cancelled) throw new Error('Cancelled');
  }
}

必要に応じて、クラスとしてラップできます。

そして最後に使用法:

doSomethingInSeries(cancellationToken);

setTimeout(cancellationToken.cancel, 5000);

タスクはすぐにキャンセルされないため、継続(待機、その後、またはキャッチ)は5秒後に正確に呼び出されないことに注意してください。このアプローチと@Bergiアプローチを組み合わせることができることを保証するため。

0