Promise-returning delay function
On 9 Sep 2014 00:13, "Domenic Denicola" <domenic at domenicdenicola.com> wrote:
(I also find the name
sleep
somewhat attractive, as a re-appropriation
from threaded contexts. await sleep(1000)
, heh.)
Sleep has blocking semantics in all languages I've used it in (eg. C, Java, Ruby, Bash, etc) so perhaps best avoid any confusion?
I've found delay
very useful and natural in the context of
Rx.Observables, where it is a method to delay each element in a stream,
although that's a slightly different use case to what's proposed here.
What's the recommended way to add capabilities to Promises? Utility functions, as suggested here? Promise subclassing/"casting"? Prototype monkey patching (I guess not, but some will do that if there's no better way...)?
Reminds me from the elegant/surprising obj::func() binding syntax Domenic showed the other day in a presentation, too.
The npm prfun
package uses both prototypes patching and subclassing,
fwiw. And it implements both Promise.delay
and
Promise.prototype.delay
. I'd be interested in feedback after folks use
it a bit.
Domenic Denicola wrote:
BTW I definitely agree that promise-returning
delay(ms)
as a bettersetTimeout
should be part of the standard library.
See twitter.com/garybernhardt/status/526435690524196864 where I'm subtweeted but with some justice. setTimeout began life taking only a program to eval, and given the general implicit-conversion mania afflicting me and others at Netscape, it converts its first argument to string source for that program. Only in Netscape 3 did the funarg-first form come in.
Sorry about that, and you're right -- at some promise-allocation and cognitive load costs, a new API could win. Another option is to have a new functional API, but I bet it won't be easy to name well or get approved.
Not sure if discussed already but I believe .cancel()
or
.forgetAboutIt()
mechanism should be proposed before introducing any
.delay()
Promises often put developers in unmanaged roads where things keep being "forgotten" and/or impossible to drop without ending up in the next chain, undesired, or the error stack, even less desired.
What I mean is that if setTimeout
is a desired pattern to replicate
through .delay(ms)
, but I really don't understand why that would be a
desired pattern at all, clearTimeout
functionality should be considered
as well.
A generic .cancel()
would be most likely the easier approach but it
should be implemented upfront.
On Mon, Oct 27, 2014 at 3:21 AM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
Not sure if discussed already but I believe
.cancel()
or.forgetAboutIt()
mechanism should be proposed before introducing any.delay()
Promises often put developers in unmanaged roads where things keep being "forgotten" and/or impossible to drop without ending up in the next chain, undesired, or the error stack, even less desired.
What I mean is that if
setTimeout
is a desired pattern to replicate through.delay(ms)
, but I really don't understand why that would be a desired pattern at all,clearTimeout
functionality should be considered as well.A generic
.cancel()
would be most likely the easier approach but it should be implemented upfront.
Cancelable promises are a completely different beast; it's not just a matter of adding a new method to Promise.prototype, it's changing the data structure pretty fundamentally. Suddenly you have a new value-affecting capability, but it's exposed outside of the constructor. You need to be able to react to a cancellation, so you can actually cancel the work being done, and possibly resolve the promise to some default value (or error).
A promise that auto-resolves after a timeout, on the other hand, is a straightforward utility function of obvious usefulness, and requires no additional work:
Promise.delay = function(ms) { return new Promise(function(r) { setTimeout(r, ms); }); }
(Not tested, but this should be approximately right.)
I thought promises were introduced to simply the "whenever it happens" pattern ... using a delay seems a pretty pointless feature to me and setTimeout worked so far (15 years?) pretty well and it is cancelable.
Regardless delay though, canceling, beast or not, should be considered before all these changes will make it "impossible" to implement, IMO
There's already a way to cancel a Promise -- use its reject
function. eg.
var reject;
Promise.delay(100/*ms*/).then(function() { reject(new Error('took too
long')); });
return new Promise(function(resolve, _reject) {
reject = _reject;
resolve(doSomethingTimeConsuming());
});
There's no need to rethink or redesign anything.
See cscott/prfun#promisetimeoutint-ms--string-message--promise
for a nice example, borrowed from bluebird
.
canceling should not pass through any errorback chain... Canceling is like an explicit abort on xhr ... Not an error, not a fulfillment, it's a third state: cancelled
So throw a different class of Error, or resolve with a different type of object. Can you substantiate your assertion better?
In my experience, regularity of control flow constructs is a feature, not a bug. I don't want to have to reason about some "third way" control flow.
The moment you pass a promise you have no control on who's adding what as callback or errorback which means you have no control over a .reject() or over a .success()
.cancel() would eventually keep both queues unresolved so that nothing should happen at all: no error handling, no error happened, and no actions needed ... it's simply canceled.
This is the same reason .fetch() API needs also events to work properly
(including progress
) ... you "cannot" base as example typeahead on
promises right now 'cause it's a mess to handle since response order is not
guaranteed but you need to handle the .cancel() case without telling the
user something else went wrong ... what you suggest, a new Noop()
where
Noop.prototype = Object.create(Error.prototype)
?
And how do you instruct unknown surrounding code that an if (err instanceof Noop)
should be the first line of every errback ?
This is the reason few devs cannot adopt current standard Promises.
.cancel()
as well as .abort()
is very important when many network
operations are in place and/or you want to switch to a different state
without waiting for the previous one to be fulfilled.
new Promise(function (resolve, reject, cancel) {});
is the dumbest idea I
could have now beside new Promise(function (resolve, reject) {}).on('cancel', cancelback).then(whatever)
one
Anyway, this would deserve IMO a thread a part but I hope this topic will be discussed at some point.
Best
On Tue, Oct 28, 2014 at 10:41 AM, Andrea Giammarchi < andrea.giammarchi at gmail.com> wrote:
The moment you pass a promise you have no control on who's adding what as callback or errorback which means you have no control over a .reject() or over a .success()
.cancel() would eventually keep both queues unresolved so that nothing should happen at all: no error handling, no error happened, and no actions needed ... it's simply canceled.
This is the same reason .fetch() API needs also events to work properly (including
progress
) ... you "cannot" base as example typeahead on promises right now 'cause it's a mess to handle since response order is not guaranteed but you need to handle the .cancel() case without telling the user something else went wrong ... what you suggest, anew Noop()
whereNoop.prototype = Object.create(Error.prototype)
?And how do you instruct unknown surrounding code that an
if (err instanceof Noop)
should be the first line of every errback ?This is the reason few devs cannot adopt current standard Promises.
.cancel()
as well as.abort()
is very important when many network operations are in place and/or you want to switch to a different state without waiting for the previous one to be fulfilled.
new Promise(function (resolve, reject, cancel) {});
is the dumbest idea I could have now besidenew Promise(function (resolve, reject) {}).on('cancel', cancelback).then(whatever)
oneAnyway, this would deserve IMO a thread a part but I hope this topic will be discussed at some point.
Best
I agree w/ Scott re: regularity of flow control, and I'd be particularly
uneasy if a cancel
method on a promise had any impact on upstream
handlers. But ISTM something like this could be made to work (even if it
looks a bit silly):
var cancelled;
var never = new Promise(function () {});
Promise.delay(100/*ms*/).then(function(value) {
return cancelled ? never : value;
}, function (error) {
return cancelled ? never : throw error;
});
function cancel() { cancel = true };
This could be cleaned up into a cancellation helper to quash downstream
promises (this kind of utility would be a good use case for an always
method if one isn't yet spec'd).
Though I do wonder what happens to promises like these that get dropped on the floor? Will downstream handlers be GC'd? Is this specified in any way? I imagine dev tools want to warn about these kinds of promises — it may be helpful to have a way to signal that a promise was intentionally quashed.
You probably want streams for typeahead, not promises.
Dean, your idea is even better as a Promise
subclass.
The subclassed cancellable promises could share a cancel flag, and
throw an exception (or resolve to a different value, whichever you
like better for the API) when the cancel flag is set. The then
method on CancellablePromise
returns another CancellablePromise
(the spec is nicely written this way), and you use Promise.resolve
to cast to a standard Promise
to end the cancellation chain at an
appropriate place.
See the Promise.bind
implementation in prfun
for an example of
using subclasses in this way:
cscott/prfun/blob/master/lib/index.js#L518
--scott
ps. Of course, to be precise, your API would be a factory function
which makes a new subclass of CancellablePromise
for each cancel
scope, since the Promise spec associates the state with the
constructor identity. (I had some small issues with the "new subclass
for each X" mechanism in an earlier attempt at creating MonadicPromise
using subclasses, but I think these were ultimately resolved in the
spec---and in that case I was creating a new subclass for each call to
Promise.resolve
, which was a whole 'nuther kettle of fish.)
I actually don't want Promises at all with network interactions but you have to explain this to all developers out there that think Promises are the Eldorado for everything asynchronous ... I found myself needing a cancel mechanism that goes beyond your outer scoped flag.
I really need to drop that action ... not to wait for it to fulfill and then do something else.
Imagine it's not about network, imagine it's "a lift" ... door are closing, someone passes in the middle, what do you do now: sound the alert after chopping the person that tried to enter in the meanwhile or change floor ... after chopping the person anyway ?
If promises could handle edge cases events too I think we could handle above situation instead of chopping that poor person.
I hope you got what I mean but again this most likely need another thread.
Best
On Tue, Oct 28, 2014 at 2:24 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
I really need to drop that action ... not to wait for it to fulfill and then do something else.
This is orthogonal to the use of Promises, which just discuss control flow.
Imagine it's not about network, imagine it's "a lift" ... door are closing, someone passes in the middle, what do you do now: sound the alert after chopping the person that tried to enter in the meanwhile or change floor ... after chopping the person anyway ?
You call the Door.interrupt()
API method to indicate that a person
is present/cancel the opening.
Again, this is orthogonal to the Promise that the Door.close()
method returned. If Door.close()
returned a Promise, then that
promise could then resolve to "cancelled" value (instead of
"success"), or else you could decide that rejecting the promise with a
DoorCloseInterrupted
exception makes more sense.
The asynchronous cancellation mechanism is not related to Promises, and shouldn't be conflated with the Promise API. The Promise mechanism just directs control flow after the door has either been successfully closed or cancelled.
Concretely:
function tryToAscend() {
return Door.close().then(function() { return raiseLift(); },
function(e) { return Door.open().delay(10*1000).then(tryToAscend); });
};
Again, you might have an error that comes from "ringTheAlarmBell()" API ... you are making the problem simple but simple is not the moment all you do is returning a Promise with binary result (success/error) without considering that when you reopen, much more could happen ... you want to branch out, not keeping doing the same action and that error could come for many reasons.
Your control flow here, if you have to take care of all cases, will become a mess full of nested callbacks ... oh, irony
I don't see any evidence that you can't handle the various cases with the usual control flow mechanisms. Remember that promises are isomorphic to the future async/await syntax.
async and await would fail here same way indeed, there should be a mechanism to branch out the binary expected result, IMO.
The other thread and the fact .NET already has something similar should probably ring some bell but it's probably there that we should discuss this.
Again, I don't see how this requires some new sort of control flow.
try to put a cancel in your same code:
function tryToAscend() {
return Door.close().then(function() { return raiseLift(); },
function(e) { return Door.open().delay(10*1000).then(tryToAscend); });
};
because when the door opens somebody cancel the floor and wants to descend instead.
Then implement the case when actually the call was simply canceled and nobody has to go anywhere ... all while delay keeps saying to each floor that after 10 seconds doors should close anyway.
Now try to pass this glorious flow around per each panel in each floor that should behave accordingly.
Can you do this via Promises? 'cause I'm pretty sure that code won't look any better than an event based one, but I can be wrong.
Follow up on esdiscuss.org/topic/promise-returning-delay-function
I think we really need that function, with AbortController support
We could do things like
const abortFetch = new AbortController();
const abortTimeout = new AbortController();
await Promise.race([
Promise.delay(5000, {signal: abortTimeout.signal}),
fetch(url, {signal: abortFetch.signal}).then(r => r.json()).then(...),
])
.finally(() => {
abortFetch.abort();
abortTimeout.abort(); // avoid mem leaks, that would call clearTimeout
under the hood
})
For Node.js we implemented this as an alternative to the current setTimeout:
const { setTimeout } = require('timers/promises')
// Or
import { setTimeout } from 'timers/promises'
Then...
await setTimeout(500)
It works very well and is straightforward to implement on the browser-side.
Spinning off a new thread...
From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Mark S. Miller Subject: RE: "use strict" VS setTimeout
IMO this should be a utility function that uses promises, not a feature of promises/promise classes themselves. So it would be e.g.
global.delay
orimport delay from "js/flow"
or something. I think in general promise utility functions are a slippery slope and I'd feel more comfortable sliding down that slope if we kept them in a module instead of grafting them onto the class itself. I'd rather reserve that for things that are core to the promise experience, either control-flow wise (Promise.prototype.finally
, perhapsPromise.any
) or for pipelining (.get
,.call
, etc.).BTW I definitely agree that promise-returning
delay(ms)
as a bettersetTimeout
should be part of the standard library.(I also find the name
sleep
somewhat attractive, as a re-appropriation from threaded contexts.await sleep(1000)
, heh.)