Break or Cancel the Promises Chain
Promise
Promise has become an important tool for managing asynchronous operations in JavaScript. However, sometimes we would still find promises kind of annoying.
Promise
// Here `then` is an equivalence to `Promise.resolve().then`.
.then(() => {
// Start.
})
.then(() => {
if (wantToBreakHere) {
// How?
}
})
.then(() => {
// Something to skip.
});
We may nest the following promise, but that will be painful if it's a long long chain.
Luckily we have a partial workaround for many promise implementations (as most of them have the helper catch
):
class BreakSignal { }
Promise
.then(() => {
// Start.
})
.then(() => {
if (wantToBreakHere) {
throw new BreakSignal();
}
})
.then(() => {
// Something to skip.
})
.catch(BreakSignal, () => {
// Use catch method to filter BreakSignal.
});
This approach is somewhat gentle, but if you have other catches (rejection handlers) in this chain, you will have to relay BreakSignal
manually.
Consider another situation than "break":
page.on('load', () => {
Promise
.then(() => asyncMethodA())
.then(result => asyncMethodB(result))
.then(result => {
// Update something...
});
});
If the load
event fires twice in a short time, we'll need to cancel the first promises chain, or at least to prevent it from updating related data or view (If this is the desired behavior).
Again, the workaround:
class BreakSignal { }
let context;
function createWrapper() {
let currentContext = context;
return function (handler) {
return function () {
if (context !== currentContext) {
throw new BreakSignal();
}
return handler.apply(undefined, arguments);
};
};
}
page.on('unload', () => {
context = undefined;
});
page.on('load', () => {
context = {};
let wrap = createWrapper();
Promise
.then(wrap(asyncMethodA))
.then(wrap(asyncMethodB))
.then(result => {
// Update something...
})
.catch(BreakSignal, () => { });
});
ThenFail
After these mess, I come up adding the ability to break or cancel a promises chain in my own promise implementation ThenFail.
Promise
.then(() => {
// Start.
})
.then(() => {
if (wantToBreakHere) {
Promise.break;
}
return Promise
.then(() => {
Promise.break;
})
.then(() => {
// Never reaches here.
});
// No need to enclose nested context.
})
.then(() => {
// Something to skip.
})
// Enclose current context to prevent from breaking too many.
// This is important if the promise chain might be used by code out of your control.
.enclose();
Actually you may also "break" each
of ThenFail:
Promise
.each([1, 2, 3], value => {
if (value > 1) {
Promise.break;
// Or asynchronously:
return Promise
.then(() => {
// Do some work.
})
.break;
}
})
.then(completed => {
if (completed) {
// Some code...
}
});
I've mentioned the concept of context
in ThenFail. And to cancel an entire promises chain (of the same context), you just need to dispose the context.
let context;
page.on('unload', () => {
context.dispose();
});
page.on('load', () => {
let promise = Promise
.then(() => asyncMethodA())
.then(result => asyncMethodB(result))
.then(result => {
// Update something...
});
context = promise.context;
});
BTW, disposing a context will also dispose nested contexts. :D