В JavaScript, как обернуть обещание в тайм-аут?
Это общий шаблон для реализации тайм-аута некоторой асинхронной функции с использованием deffered/prom:
// Create a Deferred and return its Promise
function timeout(funct, args, time) {
var dfd = new jQuery.Deferred();
// execute asynchronous code
funct.apply(null, args);
// When the asynchronous code is completed, resolve the Deferred:
dfd.resolve('success');
setTimeout(function() {
dfd.reject('sorry');
}, time);
return dfd.promise();
}
Теперь мы можем выполнить некоторую асинхронную функцию с именем myFunc
и обработать таймаут:
// Attach a done and fail handler for the asyncEvent
$.when( timeout(myFunc, [some_args], 1000) ).then(
function(status) {
alert( status + ', things are going well' );
},
function(status) {
alert( status + ', you fail this time' );
}
);
Хорошо, сделайте поворот в этой истории! Представьте себе, что сам myFunc
возвращает обещание (ПРИМЕЧАНИЕ: обещание НЕ отложено, и я не могу его изменить):
function myFunc(){
var dfd = new jQuery.Deffered();
superImportantLibrary.doSomething(function(data)){
if(data.length < 5){
dfd.reject('too few data');
}
else{
dfd.resolve('success!');
}
}, {'error_callback': function(){
dfd.reject("there was something wrong but it wasn't timeout");}
}});
return dfd.promise();
}
Теперь, если я завершу myFunc
в timeout
, я потеряю способность обрабатывать ошибки, отличные от тайм-аута. Если myFunc
испускает события прогресса, я тоже потеряю это.
Итак, возникает вопрос: как изменить функцию timeout
, чтобы она могла принимать функции, возвращающие promises, не теряя своих ошибок/информацию о ходе?
Ответы
Ответ 1
function timeout(funct, args, time) {
var deferred = new jQuery.Deferred(),
promise = funct.apply(null, args);
if (promise) {
$.when(promise)
.done(deferred.resolve)
.fail(deferred.reject)
.progress(deferred.notify);
}
setTimeout(function() {
deferred.reject();
}, time);
return deferred.promise();
}
Ответ 2
Вы всегда должны пробовать на самом низком уровне. Давайте начнем с основ.
Я буду использовать jQuery promises здесь, но это действительно должно быть сделано с более сильной библиотекой, такой как Bluebird. Пусть начнется просто, создав наш delay
как:
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
Задержка записи не делает ничего удивительного, вся наша функция задержки вызывает задержку ms
миллисекунд.
Теперь для вашей библиотеки мы хотим создать версию doSomething
, которая работает с promises:
superImportantLibrary.doSomethingAsync = function(){
var d = $.Deferred();
superImportantLibrary.doSomething(function(data){ d.resolve(data); });
return d.promise();
};
Обратите внимание, что обе наши функции задержки и doSomethingAsync
выполняют как одну вещь. Теперь начинается веселье.
function timeout(promise,ms){
var timeout = delay(ms); // your timeout
var d = $.Deferred();
timeout.then(function(){ d.reject(new Error("Timed Out")); });
promise.then(function(data){ d.resolve(data); });
return d.promise();
}
timeout(superImportantLibrary.doSomethingAsync(),1000).then(function(data){
// handle success of call
}, function(err){
// handle timeout or API failure.
});
Теперь в Bluebird весь этот код был бы:
superImportantLibrary.doSomethingAsync().timeout(1000).then(function(){
// complete and did not time out.
});
Ответ 3
Я понимаю, что это 2 года, но в случае, если кто-то ищет ответ...
Я думаю, что Бенджамин был близок в том, что вы хотите, чтобы ваш тайм-аут обрабатывался отдельно, поэтому мы начнем с его функции задержки.
function delay(ms){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); }, ms);
return d.promise();
}
Затем, если вы хотите подождать до выполнения кода, вы можете вызвать метод, который вы хотите отложить в результате этого обещания.
function timeout(funct, args, time) {
return delay(time).then(function(){
// Execute asynchronous code and return its promise
// instead of the delay promise. Using "when" should
// ensure it will work for synchronous functions as well.
return $.when(funct.apply(null, args));
});
}
Обычно это то, что я пытаюсь сделать, когда я ищу освежителя (почему я здесь). Однако вопрос заключался не в задержке выполнения, а в выдаче ошибки, если это заняло слишком много времени. В этом случае это усложняет ситуацию, потому что вы не хотите ждать тайм-аут, если вам не нужно, поэтому вы не можете просто заключить два обещания в "когда". Похоже, нам нужен еще один отложенный в миксе. (См. Ожидание разрешения первого из нескольких отложенных jQuery?)
function timeout(funct, args, time) {
var d = $.Deferred();
// Call the potentially async funct and hold onto its promise.
var functPromise = $.when(funct.apply(null, args));
// pass the result of the funct to the master defer
functPromise.always(function(){
d.resolve(functPromise)
});
// reject the master defer if the timeout completes before
// the functPromise resolves it one way or another
delay(time).then(function(){
d.reject('timeout');
});
// To make sure the functPromise gets used if it finishes
// first, use "then" to return the original functPromise.
return d.then(function(result){
return result;
});
}
Мы можем упростить это, зная, что в этом случае главный defer отклоняется только в том случае, если время ожидания происходит первым, и разрешается только в том случае, если functPromise разрешается первым. Из-за этого нам не нужно передавать functPromise в разрешение master defer, потому что это единственное, что может быть передано, и мы все еще в области видимости.
function timeout(funct, args, time) {
var d = $.Deferred();
// Call the potentially async funct and hold onto its promise.
var functPromise = $.when(funct.apply(null, args))
.always(d.resolve);
// reject the master defer if the timeout completes before
// the functPromise resolves it one way or another
delay(time).then(function(){
d.reject('timeout');
});
// To make sure the functPromise gets used if it finishes
// first, use "then" to return the original functPromise.
return d.then(function(){
return functPromise;
});
}