-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A setTimeout that returns a promise that resolves after specified time. #617
Comments
I am in support of this idea (with a better name, but we can bikeshed that later). I would like to hear if any implementers would implement this assuming I specced it. |
How about cancelation? |
I think we would have to wait for cancelable promises (in whatever form they show up in). That might be enough to sink this feature until that work is done, but I personally don't think it's necessary---adding it right now, and making it cancelable later, would be OK in my book. |
Are we absolutely sure that making this cancelable later won't be a breaking change or introduce nasty duplication like Without cancelation, this proposal is just a shortcut to a one-liner which we can (and do) live without. Achieving clearTimeout functionality with promises is a lot harder and it would be better if this proposal could solve that issue. |
If promises become cancelable in the future, this would automatically become cancelable too.
These are 2 separate issues. |
Yes, sort of. From one perspective, it could be a breaking change, since code that previously assumed a promise would never be canceled would suddenly start to have to deal with that. This is a general problem with cancelable promises across the platform and is part of the reason why I am scared to start working on them because it will start causing lots of debates. (Which I really do not want to reiterate in this thread; so let's try to drop this line of discussion.)
We can guarantee this will not happen, yes.
Even with cancelation, your statement applies. This proposal is entirely about convenience. |
Cool. 👍 |
Would TC39 not want this to be a static on |
I am pretty sure no. TC39 does not define an event loop or a notion of time; that is for the embedder. And using statics as a place to store utility methods is not a great pattern; it's forced by necessity in many cases, but in general statics should be reserved for things that vary among subclasses. You could argue this is one such thing, but I am not sure myself... |
|
Hey, copying my reply from the duplicate (thanks for finding this Domenic):
Sure, what if we allow passing an await delay(3000, { signal: controller.signal} ); In a way similar to Another interesting idea is a variant of for await(const time of interval(1000)) {
// do something, `break` here to cancel (or `.return` on the iterator)
} |
This is because it tries to circle around access to the object itself, because Promises are supposed to be immutable and have no direct object access, because apparently people want to return just a Promise. So here we have something persistent. Particularly in the case of intervals, but also in the case of a Timeout -- for example, in Node timeouts may be 'refed' and 'unrefed' from the event loop, refreshed (as the same object/id), and of course you may simply just want to cancel the timeout (which happens constantly if you are implementing any network protocol). Another angle, brought up by async foo() {
const interval = setInterval(fn, 1000)
for await (const m of interval) {
/* ... */
clearInterval(interval) // would end the iterator...
}
) Promises do not modal persistently intractable interfaces. That is the problem. (Not just, not well... they just don't at all.) Cancellable promises only help one very specific part of this issue, it should be noted. This problem isn't just isolated to timeouts either, there a lot of APIs in Node where we'd like to "have a promise version" (really "an awaitable version") but we also need to return immediate access to something with... events and handlers and stuff. Like a child process or http server. So, to model this "correctly" by allowing direct access the persistent objects in question, I have suggested that Node make timers "thenable". Which I am moving on recommending for other apis in node as well (as noted in this twitter thread about the subject (please don't reply to that though)). It's "ugly" but the other options are... well, there isn't much in terms of options given the constraints. I don't have a good answer for web browsers which for some forlorn reason return a number from |
AbortController is the web platform's primitive for cancelation, so that is what we will use for the promise-returning setTimeout analog. I can't speak to the larger issues in your message; I'm unsure how they're related to this issue (or to the web platform). |
They are related because (as sizeable group) folks want web compatibility and want to work with e.g. the whatwg to come up with things that work outside web platforms. Node is also encountering this issue of "people want an awaitable timeout", but also that has the related issues noted above. |
That AbortController/AbortSignal unifies cancellation is its most important property. Right now people are mainly familiar with AC because of fetch, but the key to its effective use is to make all cancellable APIs respect it. This allows one signal to correspond to one cancellable ‘transaction’. AC is kinda ugly to work with directly, but this is in part because cancellation is an ugly problem. I’d still rather see Node implement AbortController (as they’ve implemented URL, TextEncoder, etc) than see HTML not include it here. |
@Fishrock123 you and @domenic are not actually disagreeing on this from what I understand. You can have |
I started to strawman this out a bit: https://gist.github.com/devsnek/9686da77db0421927c84e862bd70b214 one of my main goals with this approach is to sort of streamline everything into one neat api that's small so its easy to remember and use. One of my assumptions for this is that https://github.com/tc39/proposal-cancellation moves forward, although it's not an explicit requirement. |
FYI the |
@Jack-Works as discussed previously in this thread, such a proposal is not appropriate for the JavaScript language, but instead needs to be part of the web platform. |
I didn't invent the notion of time, I leave it for the host to interpreter what does the time parameter means. I also didn't add something like "event loop" to the langauge, I'm re-using the existing Job concept for scheduling task. |
Job is not appropriate for this. Also the lack of integration with AbortSignal is a killer. Do you have something in particular against doing this in the correct standards body? |
It seems like no one is working on this in whatwg for years. And I don't like it appears on the |
Yes. So why don't you?
Node.js has
That's not true. Many features standardized outside of TC39 are implemented on multiple platforms. For example, the Encoding Standard, |
So Node need "require(...)" to use the promised timer. That also not good for cross-platform code. @domenic 's argument only works if major platforms implement it in exact same API (a global object). If they need different way to access, "are implemented in multiple platforms" doesn't provide convenience for this simple function. Hand written one is much easy than environment detect and load different native apis. |
@Jack-Works it can be imported as well in node. |
What cross-platform code? Timers are not and never were cross-platform. Node does not implement the web-timers specification and while it follows some quirks it doesn't follow others. Additionally it has its own quirks that likely go against the spec. Node does make timers interop nicely in universal code when possible - for example the new toPrimitive. Node does have some spec compliant APIs (like URL) and there it follows the same spec as browsers. |
That's why we need one. |
What about environments that run JavaScript but have no concept of timers like certain embedded environments? |
They can throw (based on spec text of my proposal). But whatever, I'm not going to push this to stage 1 now. |
I am not trying to convince you to not pursue this. Please don't take my/our comments as attacks or discouraging. You are looking to contribute in a (pretty large) area that some of us care about (Some examples: Domenic and Jordan from the specification and whatwg point of view, myself and Jordan from Node timers and I also help maintain sinon/jest's fake-timers). People are just indicating that TC39 is not the correct avenue to push this because of the scope of what's part of ECMAScript and what's part of the platform. Domenic's first ever comment to you here was to suggest you in fact work on this :] Here are some constructive ways to contribute to this (feel free to do some, or none at all):
|
Thanks I will ✨ @domenic says
With objections from implementor, it's no meaning to push this forward because it will be blocked earlier or later. (And therefore it's meaningless to present to stage 1) By accepting this irreversible conclusion, I'm OK to write platform-aware code. Since I have to write a helper function whatever I'm not so motivated in investigating timer API in any major platform cause it already has the ability to implement what I want in the user land. |
I think the Chrome/V8 requests Domenic raised (AbortSignal integration since that's the built-in cancellation mechanism, and reusing the same timer infrastructure) are pretty reasonable and I would (unsafely) assume other implementors would have similar requirements in order to avoid further fragmentation. That said such an API (With an optional AbortSignal) would address both your use case (of promise returning timers) and Chrome's (reusing existing machinery). In fact that's the approach Node took. This is entirely possible and probably (hopefully) we'll eventually get this sort of API. The thing is: doing that as a first contribution to whatwg/html sounds like a very tall order to me which is why I suggested API feedback / adding tests to the existing machinery. |
I didn't know there's a discussion about this going on (I was looking for similar topics in ES proposals). I've prepared an explainer for the same idea. The solution is different though. Maybe it would be helpful in this discussion: https://github.com/jarrodek/delayed-promises |
FYI, Node.js 15 shipped Edit 2: I'm trying writing a PR. |
Continuing from #7340 (comment) where @annevk said
see also #6201 Unfortunately doStuff();
await delay(1000);
doOtherStuff(); to doStuff();
await scheduler.postTask(() => {}, { delay: 1000 });
doOtherStuff(); @shaseley and I have discussed before whether we could add something simple like await scheduler.postTask(async () => {
// This code definitely runs at "background" priority
doStuff();
await scheduler.wait(1000);
// But what about this code? It's not the same browser task...
// but it is the same conceptual task for developers. How can we
// make it run at "background" priority?
doOtherStuff();
}, { priority: "background" }); |
I have to agree that |
Relevant:
Worth mentioning in this context:
|
Node.js also includes an implementation of Cloudflare workers also has |
@domenic couldn't |
Defining what's the current task's priority isn't that easy I guess. |
Well, we could inherit from |
You'd still face situations like await scheduler.postTask(async () => {
// This code definitely runs at "background" priority
doFirstStuff();
await scheduler.postTask(doSecondStuff, { priority: "user-blocking" });
// should we inherit from the outer "background" task
// or from the current "user-blocking" doSecondStuff one?
await scheduler.wait(1000);
doOtherStuff();
}, { priority: "background" }); But anyway, wouldn't people also want for I guess my point is that currently this idea of an "userspace task" that would somehow keep a given priority for all the inner browser tasks (including callbacks?) doesn't seem doable. And I don't see how |
I recently published an explainer for scheduler.yield() which discusses inheritance design and proposes an API shape (I'm working on prototyping/drafting a spec now). My thinking is that Note: this doesn't fix/uncomplicate the big picture @Kaiido mentions (it's not clear exactly how/if that could be done), but I think it fits in to the current world reasonably well. |
@jasnell do you think it makes sense for Node to implement @shaseley would that help for feedback/prior art if we did? (In Node terms it'd likely be just |
Is there interest from WebKit or Gecko in the Scheduler API? |
Gecko has had an implementation of the Scheduler API enabled on Nightly for quite awhile now, but there hasn't be any too convincing use cases for it, so it hasn't shipped. We've been pondering whether to remove it or ship it. (since when implementing the spec literally it doesn't really add anything to the platform). But recently in WICG/scheduling-apis#61 it has become clear that Chrome's implementation does some other scheduling too (details a bit unclear), which actually do add new capabilities. If the spec proposal gets clarified, it might convince Gecko to ship the API. |
Aside: Is there any slight possibility that this could be a global, rather than under I understand there may be Very Good Reasons (consistency, etc.) for not doing this, but I'm making a case from an all-things-considered, ergonomics perspective here - still may not be enough to outweigh the consistency/etc reasons, of course - just looking for comments here. (That said, we somehow settled on |
|
(sure, just comparing |
I really love
Promise.resolve("some value")
, however if you want to delay this resolving you have to do something much uglier likeWould love a way to create a promise that waits and resolves. I made a proposal for
Promise.after
to ES and was informed it is a better fit to recommend here.It seems a global function might make more sense in this context. Maybe something like
resolveIn(1000)
that would return a promise that resolves in 1000 milliseconds. This could even be implemented like so:And could be used like:
And could be used to resolve a specified value after a delay like so:
The text was updated successfully, but these errors were encountered: