exit-async.js 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. import {once} from 'node:events';
  2. import {DiscardedError} from '../return/final-error.js';
  3. // If `error` is emitted before `spawn`, `exit` will never be emitted.
  4. // However, `error` might be emitted after `spawn`.
  5. // In that case, `exit` will still be emitted.
  6. // Since the `exit` event contains the signal name, we want to make sure we are listening for it.
  7. // This function also takes into account the following unlikely cases:
  8. // - `exit` being emitted in the same microtask as `spawn`
  9. // - `error` being emitted multiple times
  10. export const waitForExit = async (subprocess, context) => {
  11. const [exitCode, signal] = await waitForExitOrError(subprocess);
  12. context.isForcefullyTerminated ??= false;
  13. return [exitCode, signal];
  14. };
  15. const waitForExitOrError = async subprocess => {
  16. const [spawnPayload, exitPayload] = await Promise.allSettled([
  17. once(subprocess, 'spawn'),
  18. once(subprocess, 'exit'),
  19. ]);
  20. if (spawnPayload.status === 'rejected') {
  21. return [];
  22. }
  23. return exitPayload.status === 'rejected'
  24. ? waitForSubprocessExit(subprocess)
  25. : exitPayload.value;
  26. };
  27. const waitForSubprocessExit = async subprocess => {
  28. try {
  29. return await once(subprocess, 'exit');
  30. } catch {
  31. return waitForSubprocessExit(subprocess);
  32. }
  33. };
  34. // Retrieve the final exit code and|or signal name
  35. export const waitForSuccessfulExit = async exitPromise => {
  36. const [exitCode, signal] = await exitPromise;
  37. if (!isSubprocessErrorExit(exitCode, signal) && isFailedExit(exitCode, signal)) {
  38. throw new DiscardedError();
  39. }
  40. return [exitCode, signal];
  41. };
  42. // When the subprocess fails due to an `error` event
  43. const isSubprocessErrorExit = (exitCode, signal) => exitCode === undefined && signal === undefined;
  44. // When the subprocess fails due to a non-0 exit code or to a signal termination
  45. export const isFailedExit = (exitCode, signal) => exitCode !== 0 || signal !== null;