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

Allow web frame methods to return async promises #7533

Merged
merged 5 commits into from
Nov 3, 2016

Conversation

MarshallOfSound
Copy link
Member

@MarshallOfSound MarshallOfSound commented Oct 9, 2016

Fixes #7532

Basically this handles executeJavaScript calls returning instances of Promise and resolving that promise before passing it back through IPC to the caller.

This trick works because

Promise.resolve(123).then((resolvedValue) => console.log(resolvedValue))
// This would print out 123
// Promise.resolve either immediately resolves with the given value or resolves the given promise

It allows for truly asyncronous execution like so

webContents.executeJavaScript(
  `new Promise(resolve => {
    setTimeout(() => resolve('myString'), 2000);
  });`,
  (returnValue) => {
     // returnValue === 'myString'
  }
);

This is non-blocking async execution 👍

@kevinsawicki
Copy link
Contributor

Would be great to get some spec coverage for this case.

@MarshallOfSound
Copy link
Member Author

@kevinsawicki Added an async spec 👍

event.sender.send(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_${requestId}`, resolvedResult)
})
.catch((resolvedError) => {
console.error(`An async web frame method (${method}) returned a promise that threw an error: `, resolvedError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This Feels Bad, shouldn't it rethrow on the other side?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how it can rethrow. Because it is async you can't throw Error as that would be uncatchable. And the standard callback syntax (err, result) isn't in use here so switching to that would be a massive breaking change for anyone using executeJavaScript.

We also currently allow syncronous errors to just fly through as uncaught exceptions in the renderer process so not sure how this is much different 👍

If we're OK with making the breaking change to the callback I'm happy to do that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like you could serialize the error, send it over the wire, then return a Promise.reject(new Error(thatError)), no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we want a callback that returns a rejected promise. I was under the impression that Promises didn't transition well across IPC though. I'll see if I can come up with a simple / nice way of doing this 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paulcbetts I just modified the code so that the callback syntax remains the same but the executeJavaScript method now returns a promise that resolves the the result and rejects with any thrown error. Let me know if that works for you 👍

@@ -642,12 +642,26 @@ Injects CSS into the current web page.
* `callback` Function (optional) - Called after script has been executed.
* `result`

Returns `Promise` - A promise that resolves with the result of the executed code
or is rejected if the result of the code is a rejected promise
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing . at end of sentence

var code = '(() => "' + expected + '")()'
var asyncCode = '(() => new Promise(r => setTimeout(() => r("' + expected + '"), 500)))()'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should switch these two new code strings and the existing code variable to use template strings for clarity.

if (callback) callback(result)
resolve(result)
})
ipcMain.once(`ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_ERROR_${requestId}`, (event, error) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so this event listener will hang around forever if the promise is never rejected, and the other one will hang around forever if the promise is rejected right?

Maybe we can use ELECTRON_INTERNAL_BROWSER_ASYNC_WEB_FRAME_RESPONSE_ for both cases and just have two arguments (event, result, error) and reject when error is present and resolve otherwise with result.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also (and probably should anyway) remove the event listener with .removeListener once we have recieved the first response / first error

@kevinsawicki kevinsawicki self-assigned this Oct 12, 2016
@MarshallOfSound MarshallOfSound force-pushed the async-executeJavaScript branch from efb4865 to 11851f9 Compare October 24, 2016 07:07
@MarshallOfSound
Copy link
Member Author

@kevinsawicki Just realized I hadn't updated this 😆

I think I addressed your comments 👍

@kevinsawicki kevinsawicki force-pushed the async-executeJavaScript branch from 1e4a787 to 8e20359 Compare November 3, 2016 16:51
@kevinsawicki
Copy link
Contributor

Thanks for this @MarshallOfSound 👍

@kevinsawicki kevinsawicki merged commit 7e90bb4 into master Nov 3, 2016
@kevinsawicki kevinsawicki deleted the async-executeJavaScript branch November 3, 2016 17:32
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.

An asynchronous executeJavaScript option
3 participants