Cancel Promise pattern (no cancellable promises)

Isiah Meadows isiahmeadows at gmail.com
Tue Jan 10 00:31:27 UTC 2017


Um... This isn't much different than Bluebird's `Promise.prototype.cancel`,
admittedly the least optimal of all these so far.

On Sat, Jan 7, 2017, 08:02 Igor Baklan <io.baklan at gmail.com> wrote:

> In general I thing it would be good to have something like
> [``java``(``Thread.interrupt()``)](
> https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#interrupt()),
> assuming that "Thread" here can be "async stacktrace of promises", and
> interrupt notification should be just forwarded to top entry
> ([promise-executor](
> https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Parameters))
> and handled there arbitrary. In meta code it can be approximately expressed
> like  ``promise.interrupt(interruption_config)`` ==>
> ``promise.topPromiseInAwaitChain().injectInterruptionNotification(interruption_config)``.
> So it should be up to [promise-executor](
> https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise#Parameters)
> - whether it want to complete abnormally on interruption (either with
> success or with failure), or just ignore this signal and continue execution
> without any reaction. It just assumes that general ``.then(...)``-like
> promises or ``async (...)  => {...}``-like promises should propagate
> interruption-signal transparently upward over async-stacktrace, and
> interruption-signal would be handled only in promises with executors which
> aware of interruption capability. In code it may look like
>
> ```js
> const waitAsync = (delay) => (new Promise(
>   (resOk, resErr, interuptionSignalEmitter) => {
>     const beforeComplete = () => {clearTimeout(tid);}
>     const tid = setTimeout(
>       () => {beforeComplete(); resOk();},
>       delay
>     );
>     interuptionSignalEmitter.on(
>       (interuptionConfig) => {
>         beforeComplete();
>         if (interuptionConfig && interuptionConfig.raiseError) {
>           resErr(new Error("abnormal interuption"));
>         } else {
>           resOk();
>         }
>       }
>     );
>   }
> ));
>
> // waitAsync(100).then(() => {console.log("wait ok")}).interrupt() -
> should complete successfully
> //   ("wait ok" message should be printed) but without delay in 100ms
> // waitAsync(100).then(() => {console.log("wait
> ok")}).interrupt({raiseError: true}) - should complete with failure
> //   (NO "wait ok" message should be printed) and without delay in 100ms
> ```
>
>
> So, in other words, I would rather say, that we lack something like event
> capturing pahase when we intent for abnormal-completion/cancellation. I
> mean, if we have for example some async-stacktrace and some particular
> entry in it in the middle (some running and not yet completed promise),
> then it looks very natural that we may wish to "send a signal/message"
> downward over async-stacktrace, it just can be made by throwing something
> in that entry (and that "thrown something" will be naturally propagated
> downward async-stacktrace/promises-chain). But in certain cases we may need
> to "send a signal/message" upward over async-stacktrace which should
> generally end up by handling in very top promise executor (and if that
> top-most promise in chain decide to complete execution abnormally, then all
> clean-up in promises-chain also happens "abnormally"), while if we just
> "unsubscribe" some middle entry form it's "natural parent" and "abnormally"
> assign some result to that entry, then ofcourse, all cleanup in "upper
> part" of async-stacktrace will happen later (which ofcourse also can be
> desired behavior in some cases).
>
> Jan-Ivar Bruaroey wrote:
> > Because it doesn't make fetch stop fetching, which is what people want
> > as I understand it (to not have the fetch keep going even though they've
> stopped waiting for it).
>
> Completely agree, but I would rather prefer
>
> ```js
> fetch(someUrl, someConfig).interuptOn(interruptionToken)
> ```
> vs
> ```js
> fetch(someUrl, {...someConfig, cancel: interruptionToken)
> ```
> in this case ``.interruptOn`` can be easily defined on top of
> ``.interrupt`` like ``promise.interruptOn(interruptionReasonToken)`` <==>
> ``interruptionReasonToken.then((reason) => {promise.interrupt(reason)}),
> promise``
>
>
> Bergi wrote:
> > Yes, that's what I mean. Sure, I could use `Promise.race` to get the
> > cancellation even if the non-cancellable operation resumes, but that's
> > quite ugly:
> >
> > Promise.race([
> >      promise()
> >      …chain…,
> >      cancelToken
> > ]).then(callback);
> >
> > especially when you'll need to nest that pattern. Instead, I'd just like
> > to write
> >
> > promise()
> > …chain…
> > .then(callback, cancelToken)
> >
> > with the same behaviour.
>
> Very reasonable. left-to-right ``.`` chaining is much more readable and
> concise then wrap-like expression chaining.
> But I would rather put ``cancelToken`` somewhere else, not in ``.then``
> call.
> From my perspective it can look like
>
> ```js
> Promise.race([
>      promise()
>      …chain…,
>      cancelToken
> ]).then(callback);
> ```
> <==>
> ```js
> promise() …chain… cancellable().cancelOn(cancelToken).then(callback);
> ```
> Where ``Promise::cancellable`` (``Promise.prototype.cancellable``) can
> return some sort of "wrapper" - ``CancellablePromise(targetPromise)``
> which in turns can be implemented based on ``Promise.race([targetPromise,
> CancellablePromise::privateInternalCancelToken])`` and that
> ``privateInternalCancelToken`` can be fully controlled by
> ``CancellablePromise`` itself and completed(fulfilled/rejected) when ever
> it needed (whether by direct call to
> ``cancellablePromise.completeNow(...)`` or caused by completion of some
> promise passed to ``.cancelOn`` method)
>
> And finally
>
> Bergi wrote:
> > I'd however love to be able to cancel specific chaining operations,
> > i.e. `then` callbacks.
>
> As for me, definitely such kind of possibility should be available.
> However we can not unsubscribe ``then``-callback same easily as regular
> event handler. Since at least [some sort of ``finally``-callbacks](
> https://www.npmjs.com/package/promise.prototype.finally) should always
> work on any item in remaining promises chain. So promise
> then-unsubscription is tightly bound to providing immediate completion
> result for that target promise (which would be used instead of callback
> invocation result). And still it can be accomplished by some kind of
> third-party solutions, (like in  snippets above) like
> ``promise.cancellable().then(() =>
> {doSomth()}).completeImmediately({error:new Error("unsubscribed
> abnormally")})``. If ``.cancellable()`` returns some wrapped instance like
> ``CancellablePromise(targetPromise)`` then ofcourse ``CancellablePromise``
> can re-implement ``.then``, ``.catch``, etc by wrapping passed functions
> and by wrapping ``targetPromise.then(...wrappedCallbacks)`` with
> appropriate ``Promise.race([...])`` like
> ``Promise.race([targetPromise.then(...wrappedCallbacks),
> internalCancellationToken])``, and then when ``.completeImmediately``
> invoked coordinate appropriately wrapped callbacks and
> ``internalCancellationToken`` to prevent initial callbacks from being
> executed and to complete "overall resulting promise" by the means of
> ``internalCancellationToken``.
> But. I think disregarding that all of this stuff can be (more or less)
> implemented in 3-rd parity libraries, it would be much better if it was
> supported natively.
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170110/d5bfd02c/attachment-0001.html>


More information about the es-discuss mailing list