79689992

Date: 2025-07-04 10:01:58
Score: 1
Natty:
Report link

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());
  }
});

What's the difference?

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
  });
}

Why does this matter?

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

Why can an asynchronous callback still work correctly?

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

Reasons:
  • Long answer (-1):
  • Has code block (-0.5):
  • Contains question mark (0.5):
  • User mentioned (1): @norlihazmey-ghazali
  • Low reputation (1):
Posted by: flexopticalHenno