Тайм-аут в async/wait
Я с 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!!
});
Любые идеи о том, как разрешить?
Ответы
Ответ 1
Вы можете использовать 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
, не обернув его обещанием.
Ответ 2
Хорошо, я нашел этот способ:
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, поскольку стратегия асинхронности отправит нам выделение слишком большого количества объектов, которые нужны какой-то другой стратегии, но это не по теме.
Ответ 3
Проблема с @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 подход.