Does not work on async await chain
Closed this issue · 2 comments
I have this test which uses async/await but it does not appear to cancel the promise
it('promiseChain partial', async () => {
let f = 0;
const cp = cancelable(
(async () => {
await new Promise((r) => setTimeout(() => r(), 100));
f = 1;
await new Promise((r) => setTimeout(() => r(), 100));
f = 2;
await new Promise((r) => setTimeout(() => r(), 100));
f = 3;
await new Promise((r) => setTimeout(() => r(), 100));
f = 4;
expect(true).toBe(false);
})()
);
await new Promise((r) => setTimeout(() => r(), 250));
cp.cancel();
expect(f).toBe(2);
});
Yes but your main promise is canceled:
let f = 0;
const cp = cancelable(
(async () => {
await cancelable(new Promise((r) => setTimeout(() => r(), 100)));
f += 1;
await cancelable(new Promise((r) => setTimeout(() => r(), 100)));
f += 1;
await cancelable(new Promise((r) => setTimeout(() => r(), 100)));
f += 1;
await cancelable(new Promise((r) => setTimeout(() => r(), 100)));
f += 1;
})()
);
(async () => {
await cp;
f += 1;
})();
cp.then(() => (f += 1));
await delay(250);
cp.cancel();
await delay(250);
expect(f).toBe(4);
Indeed the inner promises are not canceled, you need extra logic to handle your use-case if you want to. How the library can have access to these inner promises? It's not chaining such as promise.then().then()
. A workaround could be:
const cp = cancelable(
(async () => {
await new Promise((r) => setTimeout(() => {
if (!cp.isCanceled()) r();
}, 100));
f = 1;
await new Promise((r) => setTimeout(() => {
if (!cp.isCanceled()) r();
}, 100));
f = 2;
await new Promise((r) => setTimeout(() => {
if (!cp.isCanceled()) r();
}, 100));
f = 3;
await new Promise((r) => setTimeout(() => {
if (!cp.isCanceled()) r();
}, 100));
f = 4;
})()
);
But it's not elegant... Another approach could be to use a generator, here a quick example:
async function start() {
async function run(gen) {
let iter = gen();
let res;
let val;
while (!res?.done) {
if (typeof gen.isCanceled === "function" && gen.isCanceled()) {
return val;
}
res = iter.next(val);
val = await res.value;
}
return val;
}
const cancelable = (gen) => {
let isCanceled = false;
gen.cancel = () => {
isCanceled = true;
};
gen.isCanceled = () => isCanceled;
return gen;
};
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let count = 0;
const generator = cancelable(function* () {
count = 0;
yield delay(100);
count += 1;
yield delay(100);
count += 1;
yield delay(100);
count += 1;
yield delay(100);
count += 1;
return count;
});
run(generator);
await delay(250);
generator.cancel();
await delay(250);
console.log("count:", count); // count: 2
}
start();
Live demo here: https://codesandbox.io/s/youthful-dubinsky-wf1vk?file=/src/index.js
(notice that it can be improved because here you can't re-use the generator after a first cancellation)
Thanks for the tip. I am thinking of looking into whether the babel
can be tweaked to replace the Promise used with your CancelablePromise
so that I can simply call cancel() on the promise