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
LibJS: Add initial support for Promises #1706
Conversation
75a966a
to
21e6e8a
Compare
cf801e3
to
ec30474
Compare
7497e04
to
17cd2f0
Compare
00539b6
to
ff224b0
Compare
Alright... almost exactly one year later, I'm happy to say this is finally done - see checklist in PR description / commit messages for details. Rebased to use the latest and greatest LibJS APIs, refactored/rewrote about half of it, added a bunch of tests, made it work in LibWeb, fixed all the bugs I know of. Enjoy this (Lagom I promise this is not an April Fools' joke. |
81cb267
to
e507e2d
Compare
} | ||
|
||
// 9.4.4 HostEnqueuePromiseJob, https://tc39.es/ecma262/#sec-hostenqueuepromisejob | ||
void VM::enqueue_promise_job(NativeFunction& job) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if this should be done now, but this needs to somehow be overridable in the future for integration with HTML event loops:
https://html.spec.whatwg.org/multipage/webappapis.html#hostenqueuepromisejob
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely one for later - from what I've seen LibWeb isn't driven by an event loop at all (yet), e.g. timers just run their callbacks right away instead of queuing a task, so I just embraced that and did this: 192f3a3
Since nothing would want to subclass JS::VM
(probably), we might just add a on_something
function and call that if it exists, leaving queueing and therefore running the job to whatever owns the VM.
Almost a year after first working on this, it's finally done: an implementation of Promises for LibJS! :^) The core functionality is working and closely following the spec [1]. I mostly took the pseudo code and transformed it into C++ - if you read and understand it, you will know how the spec implements Promises; and if you read the spec first, the code will look very familiar. Implemented functions are: - Promise() constructor - Promise.prototype.then() - Promise.prototype.catch() - Promise.prototype.finally() - Promise.resolve() - Promise.reject() For the tests I added a new function to test-js's global object, runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs(). By design, queued jobs normally only run after the script was fully executed, making it improssible to test handlers in individual test() calls by default [2]. Subsequent commits include integrations into LibWeb and js(1) - pretty-printing, running queued promise jobs when necessary. This has an unusual amount of dbgln() statements, all hidden behind the PROMISE_DEBUG flag - I'm leaving them in for now as they've been very useful while debugging this, things can get quite complex with so many asynchronously executed functions. I've not extensively explored use of these APIs for promise-based functionality in LibWeb (fetch(), Notification.requestPermission() etc.), but we'll get there in due time. [1]: https://tc39.es/ecma262/#sec-promise-objects [2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
We now run queued promise jobs after calling event handler, timer, and requestAnimationFrame() callbacks - this is a bit ad-hoc, but I don't want to switch LibWeb to use an event loop right now - this works just fine, too. We might want to revisit this at a later point and do tasks and microtasks properly.
We now leverage the VM's promise rejection tracker callbacks and print a warning in either of these cases: - A promise was rejected without any handlers - A handler was added to an already rejected promise
I figured I'd start working on Promises - still WIP. Not aiming for the full Promise API in this PR, but at least
then()
,catch()
,finally()
should be working.The code, including some function names is closely modelled after the spec: https://tc39.es/ecma262/#sec-promise-objects
I'll probably put a checklist here and start writing tests tomorrow to see what the actual limitations currently are.Here it is:Promise()
new Promise()
Promise.prototype.then()
onResolve
onReject
Promise.prototype.catch()
(there's a catch though, pun intended - the parser doesn't understandnew Promise(...).catch(...)
, so we have to usenew Promise(...)["catch"](...)
for now)Promise.prototype.finally()
Not sure about the non-prototype functions yet, might be out of scope for this PR:
Promise.all()
Promise.allSettled()
Promise.race()
Promise.reject()
(should be relatively easy)Promise.resolve()
(should be relatively easy)The following is outdated, see further down in this PR for updates.
It can run this for example:
Browser console in Firefox for comparison:
Or this, with delayed resolving (which is usually the case):
Again, Firefox for comparison: