Skip to content
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

Merged
merged 4 commits into from Apr 2, 2021

Conversation

linusg
Copy link
Member

@linusg linusg commented Apr 8, 2020

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 understand new Promise(...).catch(...), so we have to use new 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:

new Promise((resolve, reject) => resolve("foo"))
    .then(value => {
        console.log("then() received " + value);
        return value + "bar";
    })
    .then(value => {
        console.log("then() received " + value);
    });

image

Browser console in Firefox for comparison:

image

Or this, with delayed resolving (which is usually the case):

image

Again, Firefox for comparison:

image

@linusg linusg force-pushed the libjs-promise branch 2 times, most recently from 75a966a to 21e6e8a Compare April 9, 2020 15:10
@linusg linusg marked this pull request as draft April 9, 2020 15:44
@linusg linusg force-pushed the libjs-promise branch 19 times, most recently from cf801e3 to ec30474 Compare April 16, 2020 19:25
@linusg linusg force-pushed the libjs-promise branch 6 times, most recently from 7497e04 to 17cd2f0 Compare April 20, 2020 13:08
@linusg linusg force-pushed the libjs-promise branch 17 times, most recently from 00539b6 to ff224b0 Compare April 1, 2021 20:14
@linusg
Copy link
Member Author

linusg commented Apr 1, 2021

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 js) screenshot showcasing some basic functionality:

image


I promise this is not an April Fools' joke.

@linusg linusg changed the title [WIP] LibJS: Add initial support for Promises LibJS: Add initial support for Promises Apr 1, 2021
@linusg linusg marked this pull request as ready for review April 1, 2021 20:32
@linusg linusg force-pushed the libjs-promise branch 2 times, most recently from 81cb267 to e507e2d Compare April 1, 2021 22:36
}

// 9.4.4 HostEnqueuePromiseJob, https://tc39.es/ecma262/#sec-hostenqueuepromisejob
void VM::enqueue_promise_job(NativeFunction& job)
Copy link
Member

@Lubrsi Lubrsi Apr 1, 2021

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

Copy link
Member Author

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
@awesomekling awesomekling merged commit 96121dd into SerenityOS:master Apr 2, 2021
@linusg linusg deleted the libjs-promise branch April 2, 2021 08:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants