admin管理员组文章数量:1323317
Let's say I have a search function to make an HTTP call. Every call can take a different amount of time. So I need to cancel the last HTTP request and wait only for the last call
async function search(timeout){
const data = await promise(timeout)
return data;
}
// the promise function is only for visualizing an http call
function promise(timeout){
return new Promise(resolve,reject){
setTimeout(function(){
resolve()
},timeout)
}
}
search(200)
.then(function(){console.log('search1 resolved')})
.catch(function() {console.log('search1 rejected')})
search(2000)
.then(function(){console.log('search2 resolved')})
.catch(function(){console.log('search2 rejected')})
search(1000)
.then(function(){console.log('search3 resolved')})
.catch(function(){console.log('search3 rejected')})
Need to see "search1 resolved" "search2 rejected" "search3 resolved"
How can I achieve this scenario?
Let's say I have a search function to make an HTTP call. Every call can take a different amount of time. So I need to cancel the last HTTP request and wait only for the last call
async function search(timeout){
const data = await promise(timeout)
return data;
}
// the promise function is only for visualizing an http call
function promise(timeout){
return new Promise(resolve,reject){
setTimeout(function(){
resolve()
},timeout)
}
}
search(200)
.then(function(){console.log('search1 resolved')})
.catch(function() {console.log('search1 rejected')})
search(2000)
.then(function(){console.log('search2 resolved')})
.catch(function(){console.log('search2 rejected')})
search(1000)
.then(function(){console.log('search3 resolved')})
.catch(function(){console.log('search3 rejected')})
Need to see "search1 resolved" "search2 rejected" "search3 resolved"
How can I achieve this scenario?
Share edited Sep 19, 2019 at 12:38 trincot 352k36 gold badges272 silver badges325 bronze badges asked Sep 18, 2019 at 21:15 Eran AbirEran Abir 1,0975 gold badges20 silver badges35 bronze badges 17- If you have multiple promises chained together- the chain will stop when one of them is rejected. You don't have them chained so they will all execute every time. – chevybow Commented Sep 18, 2019 at 21:19
- Aksi bit rekated ti question but you should move to async await its much simplier – Loki Commented Sep 18, 2019 at 21:20
- this is not the scenario I want. I need to cancel only the last call of promise function if not yet resolved the promise function just visualize an async call like an API i want only the last promise to be resolve or any other promise that resolved before 1000 ms – Eran Abir Commented Sep 18, 2019 at 21:20
-
Your
Promise
construction should benew Promise((resolve, reject) => ...)
, also, what's the problem loggingpromise2 resolved
? – Washington Guedes Commented Sep 18, 2019 at 21:21 -
2
Your question is ambiguous. If really the last promise should be cancelled when a new request es in, your code example should produce "search1 rejected", not "search1 resolved", since the second call of
search
should identify the previous promise as not resolved. If however, you want to give precedence to which-ever promise resolves first, then the output should be "search1 resolved", and the other two rejected. Please clarify the logic in your question, and make the code consistent with that explanation. – trincot Commented Sep 19, 2019 at 12:42
3 Answers
Reset to default 3Promises aren't cancelable as such, but are cancelled in a limited sense by causing them to be rejected.
With that in mind, cancellation can be achieved with a small amount of elaboration around Promise.race()
and the promise-returning function you wish to be cancelable.
function makeCancellable(fn) {
var reject_; // cache for the latest `reject` executable
return function(...params) {
if(reject_) reject_(new Error('_cancelled_')); // If previous reject_ exists, cancel it.
// Note, this has an effect only if the previous race is still pending.
let canceller = new Promise((resolve, reject) => { // create canceller promise
reject_ = reject; // cache the canceller's `reject` executable
});
return Promise.race([canceller, fn.apply(null, params)]); // now race the promise of interest against the canceller
}
}
Assuming your http call function is named httpRequest
(promise
is confusing):
const search = makeCancellable(httpRequest);
Now, each time search()
is called, the cached reject
executable is called to "cancel" the preceding search (if it exists and its race has not already fulfilled).
// Search 1: straightforward - nothing to cancel - httpRequest(200) is called
search(200)
.then(function() { console.log('search1 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
// Search 2: search 1 is cancelled and its catch callback fires - httpRequest(2000) is called
search(2000)
.then(function() { console.log('search2 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
// Search 3: search 2 is cancelled and its catch callback fires - httpRequest(1000) is called
search(1000)
.then(function() { console.log('search3 resolved') })
.catch(function(err) { console.log('search3 rejected', err) });
If necessary, the catch callbacks can test err.message === '_cancelled_'
in order to distinguish between cancellation and other causes of rejection.
You can define a factory function to encapsulate your search()
method with the requested cancellation behavior. Note that while Promise
constructors are normally considered an anti-pattern, it is necessary in this case to keep a reference to each reject()
function in the pending
set in order to implement the early cancellation.
function cancellable(fn) {
const pending = new Set();
return function() {
return new Promise(async (resolve, reject) => {
let settle;
let result;
try {
pending.add(reject);
settle = resolve;
result = await Promise.resolve(fn.apply(this, arguments));
} catch (error) {
settle = reject;
result = error;
}
// if this promise has not been cancelled
if (pending.has(reject)) {
// cancel the pending promises from calls made before this
for (const cancel of pending) {
pending.delete(cancel);
if (cancel !== reject) {
cancel();
} else {
break;
}
}
settle(result);
}
});
};
}
// internal API function
function searchImpl(timeout) {
return new Promise((resolve, reject) => {
setTimeout(resolve, timeout);
});
}
// pass your internal API function to cancellable()
// and use the return value as the external API function
const search = cancellable(searchImpl);
search(200).then(() => {
console.log('search1 resolved');
}, () => {
console.log('search1 rejected');
});
search(2000).then(() => {
console.log('search2 resolved');
}, () => {
console.log('search2 rejected');
});
search(1000).then(() => {
console.log('search3 resolved');
}, () => {
console.log('search3 rejected');
});
search(500).then(function() {
console.log('search4 resolved');
}, () => {
console.log('search4 rejected');
});
This factory function utilizes the insertion-order iteration of Set
to cancel only the pending promises returned by calls made before the call returning the promise that has just settled.
Note that cancelling the promise using reject()
does not terminate any underlying asynchronous process that the creation of the promise has initiated. Each HTTP request will continue to pletion, as well as any of the other internal handlers that are called within search()
before the promise is settled.
All cancellation()
does is cause the internal state of the returned promise to transition from pending to rejected instead of fulfilled if a later promise settles first so that the appropriate handler(s) for promise resolution will be called by the consuming code.
Similar to the answer of PatrickRoberts, I would suggest to use a Map
to maintain a list of pending promises.
I would however not maintain a reference to the reject
callback outside of the promise constructor. I would suggest to abandon the idea of rejecting an outdated promise. Instead, just ignore it. Wrap it in a promise that never resolves or rejects, but just remains a dead promise object that does not ever change state. In fact, that silent promise could be the same one for every case where you need it.
Here is how that could look:
const delay = (timeout, data) => new Promise(resolve => setTimeout(() => resolve(data), timeout));
const godot = new Promise(() => null);
const search = (function () { // closure...
const requests = new Map; // ... so to have shared variables
let id = 1;
return async function search() {
let duration = Math.floor(Math.random() * 2000);
let request = delay(duration, "data" + id); // This would be an HTTP request
requests.set(request, id++);
let data = await request;
if (!requests.has(request)) return godot; // Never resolve...
for (let [pendingRequest, pendingId] of requests) {
if (pendingRequest === request) break;
requests.delete(pendingRequest);
// Just for demo we output something.
// Not needed in a real scenario:
console.log("ignoring search " + pendingId);
}
requests.delete(request);
return data;
}
})();
const reportSuccess = data => console.log("search resolved with " + data);
const reportError = err => console.log('search rejected with ' + err);
// Generate a search at regular intervals.
// In a real scenario this could happen in response to key events.
// Each promise resolves with a random duration.
setInterval(() => search().then(reportSuccess).catch(reportError), 100);
本文标签: javascriptHow to cancel last Promise if not resolvedStack Overflow
版权声明:本文标题:javascript - How to cancel last Promise if not resolved? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742142848a2422659.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论