Building on the answers from @norlihazmey-ghazali here's what's working for me, with explanation below:
let isCleanedUp = false;
async function cleanUp() {
// waiting for some operations to be done
isCleanedUp = true;
}
app.on("before-quit", (event) => {
if (!isCleanedUp) {
event.preventDefault();
cleanUp().then(() => app.quit());
}
});
The callback function for the before-quit
event is not asynchronous. Passing an async function is the same as passing a synchronous function that returns a pending promise.
async function asynchronous1() {
// ...
}
function asynchronous2() {
return new Promise((resolve, reject) => {
// ...
resolve();
});
}
Calling either of those two functions outside of an async
context returns a promise that can either be stored somewhere or handled whenever it settles.
function synchronous() {
const pendingPromise = asynchronous1();
// synchronous code, promise is still pending
pendingPromise.then(() => {
// inside this context the promise has settled
});
}
There's no indicator in Electron documentation that the callback is handled as an asynchronous function. So no matter what the callback returns, Electron will continue with its synchronous code without waiting for a returned promise to settle.
Using the code from earlier, it could look something like this:
function async callback(event) {
console.log("called callback");
if (!isCleanedUp()) {
console.log("not cleaned up yet");
event.preventDefault();
await cleanUp();
console.log("quitting after cleanup");
app.quit();
}
}
function electronHandling(event) {
console.log("call all before-quit handlers");
callback(event);
if (isDefaultPrevented(event)) {
return;
}
console.log("call all quit handlers");
// ...
console.log("closing the app");
// ...
}
Seeing that electronHandling
is synchronous, the output of above code looks like this:
call all before-quit handlers
call all quit handlers
closing the app
called callback
not cleaned up yet
quitting after cleanup
With a small adjustment in the callback you can make the execution order more obvious:
function callback(event) {
console.log("synchronous callback");
return new Promise((resolve, reject) {
console.log("called callback");
if (!isCleanedUp()) {
console.log("not cleaned up yet");
event.preventDefault();
cleanUp().then(() => {
console.log("quitting after cleanup");
app.quit();
resolve();
});
}
});
}
This second callback will produce the following output:
call all before-quit handlers
synchronous callback
call all quit handlers
closing the app
called callback
not cleaned up yet
quitting after cleanup
The action of a promise is executed as a microtask. While the handling of calling the registered callbacks is synchronous, there might still be some asynchronous tasks in the Electron code that allow microtasks to execute before the app has fully quit. So it is possible that a well placed console.log()
is giving a microtask enough time to run a cleanup that's not within the javascript thread even though it is not awaited properly. Which is not fun to debug by adding logs, so prefer the proper solution over one that's working by chance