-
Notifications
You must be signed in to change notification settings - Fork 468
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
C++ implementation of promise #137
Conversation
LGTM. @gabrielschulhof Can you take a look at this, since you implemented the C APIs for promises? For updating |
I like the idea of the |
Depends on what the goal is? To share code we can also just extract common code into a common superclass. Especially if we want minimal overhead. Of course a single implementation that can use both promises and callbacks would make the scenario where a user wants to use both possible. Personally I feel that promises are the future and a class that supports it with minimal overhead is worth separating from callbacks. But I imagine there are many arguments that can be made... :) |
It would be nice if this could get merged so we can continue with adding higher level abstractions using promises. |
Would like @gabrielschulhof input. @gabrielschulhof do you think you'll be able to get to this soon ? |
@mhdawson Do you have any idea when this will be added? |
@corymickelson will try to talk to @gabrielschulhof in the weekly meeting this Thursday to see when/if he is going to be able to review. |
@mhdawson @gabrielschulhof: Any updated ETA? I would really like to see this (or something similar) land. |
I’d propose just landing this in the next few days if there’s no further discussion here |
Would you expect that any use of these V8 Promises results in asynchronous fulfillment on the JavaScript side? Because that is what I am seeing even when my C++ code is synchronous. Don't get me wrong, in my opinion that is a good behavior for Promises... I was just surprised by it. I'm assuming that is because when creating/resolving a V8 Promise object, it is automatically scheduled to be fulfilled with the event loop on the next tick (or similar end-of-this-iteration/start-of-next-iteration concept beyond the "tick" time slice) rather than allowing for immediate fulfillment. Sound about right? |
P.S. Specific synchronous code in question: |
Reading up a bit more, I see that Node handles Promise fulfillment with a "promises microtask queue" (immediately following the " As such, with any Node-native Edified. 🎓 |
@gabrielschulhof agreed to review soon in our last Thrusday call |
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.
LGTM
I'll look to land this week, just going to give @gabrielschulhof a couple more days to comment, otherwise will go ahead and land. |
test/promise.js
Outdated
assert.strictEqual(binding.promise.isPromise(resolving), true); | ||
|
||
resolving.then(function(value) { | ||
resolved = value; |
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.
Using common.mustNotCall()
and common
.mustCall()` from Node might be a better way of asserting correct asynchronous behaviour.
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.
By using, do you mean copying the functions to this project? Because it's not exported by Node, is it?
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.
Yeah, we'd pretty much have to copy them. TBH, we should be copying the whole test/addons-napi directory, because we are diverging somewhat from the implementation present in Node.js, so we need to make sure that those tests pass too. Of course that's waaay beyond the scope of this PR, but that one piece of functionality (mustCall()
and mustNotCall()
) would come in handy already.
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'd suggest that copying those might make sense, I don't think we necessarily need to delay this PR for that unless @rolftimmermans believes its relatively easy to do.
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.
Sure, I added the test helpers that are useful for this test case. I imagine it only makes sense to add others once they're needed.
test/promise.cc
Outdated
} | ||
|
||
Value ResolvePromise(const CallbackInfo& info) { | ||
auto resolver = Promise::Resolver::New(info.Env()); |
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 feel too strongly about this, but what about s/Resolver/Deferred/g
? The only reason I mention it is that this makes things look too much like V8. IMO using "Deferred" distances us from the engine.That's also why I used "deferred" in the C API.
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.
That makes sense. I did copy Resolver
from v8.
@rolftimmermans thanks for your patience looking very close. |
@gabrielschulhof I think your comments have been addressed except the suggestion to pull in some of the test framework. I'd like to land this tomorrow unless you object. |
landing |
I'm getting merge conflicts which I think are due to interveening changes as github reports that there are no conflicts. @rolftimmermans could you squash down to 1 commit ? |
Sure, squashed & pushed! |
@rolftimmermans thanks ! You always respond so quickly :) |
PR-URL: #137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
Landes as 8ffea15. Thanks for taking the time to do this. |
@rolftimmermans: Were you going to create a PR with your |
@JamesMGreene Yes, but while working on it in my project I found some critical (for me) usability issues with the
Problem 1 can be solved a (breaking?) API change for AsyncWorker/AsyncResolver. Problem 2 is more tricky. The way I solved this now is to create a different implementation that takes two lambdas and converts them to Does anyone have any thoughts on this? Ideally AsyncWorker and AsyncResolver (or any alternative) would work with a similar API of course. |
@rolftimmermans Just stumbled across your post regarding the usability of AsyncWorker and the AsyncResolver derivative; I ran into the exact same issues and had been working on a similar implementation in parallel. Came to the same conclusions - the only way to properly fix the issues would be a breaking change to AsyncWorker. Unless I'm missing something, AsyncWorker didn't even allow passing a return value back out to the callback function. My code is plugging into some custom encryption logic that returns custom Error objects with additional metadata on failures; the current design "string as error" design prevents me from doing that. Currently thinking something along these lines:
Rolf, let me know if you're interested in teaming up on any of this and pitching a new async worker API pattern to the node-addon-api group. |
Sounds good, I'll have a look to see if I can distill what I'm currently using in the C++ code. Larger APIs may have many different async tasks so I'd be in favour of something that doesn't require defining classes for every single scenario; something that accepts lambdas would be nice. |
So what I have now is this:
It allows you to do this:
Advantages I found this approach has:
Disadvantages I have found so far:
Perhaps these issues can be solved, I'm not sure yet. |
I just created #231 so that we can discuss async worker improvements in a single spot; feel free to toss ideas there if you think it will be useful. Comments and thoughts:
The biggest constraint I have at the moment is the need for two callbacks/lambdas - one for the execution, and another for the completion. The completion runs in the main Node.js event loop, so it has access to the full scope of Javascript types and operations that can be executed. Looking at #223, I wonder if something similar could be used to return Error objects and return types directly out of the Execute callback somehow. Regardless of whether or not that's possible, I think we can wrap the "complete callback" with some magic that 1) accepts a napi_value as from the complete function, 2) maps thrown exceptions to Javascript Errors 3) recognizes when Javascript Error is "thrown" with C++ exceptions turned off. The idea would be the "complete callback wrapper" would then somehow bridge the return value and/or Error to a Promise or Callback. |
Having a second callback/lambda is required to create a custom error because a v8/n-api error object cannot be created in a background thread. I'm fine with having a default implementation that does the sane thing 90% of the time, but to overcome the custom error issue it needs to be possible to override it. I will look at |
Hi, Then I created an |
@AlexandreBossard I'd recommend opening a new issue if you are experiencing problems. Also, please include more of your actual code for context when you create it. Thanks! |
PR-URL: nodejs/node-addon-api#137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
PR-URL: nodejs/node-addon-api#137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
PR-URL: nodejs/node-addon-api#137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
PR-URL: nodejs/node-addon-api#137 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Jason Ginchereau <[email protected]>
I noticed #126 was open and decided to implement it because I needed it for my experiments in converting an existing project to N-API. Especially an
AsyncWorker
-like class that I'd like to open a separate PR for once this is accepted. (Implementation is already done)I don't know what the policy is for syncing
node_api.h
/node_api.cc
etc, but they are also updated to a more recent version.The API allows you to create a
Promise::Resolver
Promise::Deferred
that you can use to retrieve the associatedPromise
; as well as resolve/reject that promise.