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!!
});
解決方法に関するアイデアはありますか?
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でラップせずに使用できません。
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を非同期戦略として使用すると、他の戦略が必要とするオブジェクトをあまりにも多く割り当てることになりますが、これはトピック外です。
@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アプローチを組み合わせることができることを保証するため。