3

With top-level await accepted into ES2022, I wonder if it is save to assume that await import("./path/to/module") has no timeout at all. Here is what I’d like to do:

// src/commands/do-a.mjs

console.log("Doing a...");
await doSomethingThatTakesHours();
console.log("Done.");
// src/commands/do-b.mjs

console.log("Doing b...");
await doSomethingElseThatTakesDays();
console.log("Done.");
// src/commands/do-everything.mjs

await import("./do-a");
await import("./do-b");

And here is what I expect to see when running node src/commands/do-everything.mjs:

Doing a...
Done.
Doing b...
Done.

I could not find any mentions of top-level await timeout, but I wonder if what I’m trying to do is a misuse of the feature. In theory Node.js (or Deno) might throw an exception after reaching some predefined time cap (say, 30 seconds).

Here is how I’ve been approaching the same task before TLA:

// src/commands/do-a.cjs
import { autoStartCommandIfNeeded } from "@kachkaev/commands";

const doA = async () => {
  console.log("Doing a...");
  await doSomethingThatTakesHours();
  console.log("Done.");
}

export default doA;

autoStartCommandIfNeeded(doA, __filename);
// src/commands/do-b.cjs
import { autoStartCommandIfNeeded } from "@kachkaev/commands";

const doB = async () => {
  console.log("Doing b...");
  await doSomethingThatTakesDays();
  console.log("Done.");
}

export default doB;

autoStartCommandIfNeeded(doB, __filename);
// src/commands/do-everything.cjs
import { autoStartCommandIfNeeded } from "@kachkaev/commands";
import doA from "./do-a";
import doB from "./do-b";

const doEverything = () => {
  await doA();
  await doB();
}

export default doEverything;

autoStartCommandIfNeeded(doEverything, __filename);

autoStartCommandIfNeeded() executes the function if __filename matches require.main?.filename.

Alexander Kachkaev
  • 842
  • 15
  • 29
  • 1
    `await` does not have any timeout built in, whether top level or not top level. – jfriend00 Aug 07 '21 at 17:34
  • Note: I don't see any `try/catch` or `.catch()` used with any of your promises. If any of these can reject, you will not have a controlled situation. – jfriend00 Aug 07 '21 at 17:37
  • Thanks @jfriend00! What do you mean by a controlled situation? As long as we don't start running `do-b` if `do-a` fails, the flow would work for me. I expect a non-zero exit code and a stack trace, should `doSomethingThatTakesHours()` throw (i.e. reject). – Alexander Kachkaev Aug 07 '21 at 17:46
  • 1
    Yeah, outputting a stack trace to stderr is what I would consider a non-controlled situation. You don't control what happens when there's an error. It will just exit without running any of your code. Generally, not what how you would design something. I mentioned the lack of error handling because that is a very common mistake when programming with `await`. But, if you want it that way, go ahead. – jfriend00 Aug 07 '21 at 17:53
  • You raise a good point actually! When I use `autostartCommandIfNeeded()`, I can control how to show uncaught exceptions and thus improve the command UX. This gets lost with top-level await, but I guess we can fix it via [`process.on('uncaughtException', handler)`](https://nodejs.org/api/process.html#process_event_uncaughtexception). E.g. we can add `import "./path/to/beautify-uncaught-exceptions"` to each command and that’ll do the job! The commands will remain free from top-level function definitions and invocations. That seems to be an unnecessary overhead if there is no TLA timeout! – Alexander Kachkaev Aug 07 '21 at 18:23
  • It doesn't have to get lost. Just use a `try/catch` around any `await` to catch rejections. If you WANT a timeout, then you have to implement it yourself. Promises in Javascript do not have built-in timeouts. – jfriend00 Aug 07 '21 at 18:36

2 Answers2

1

As far as I know, there is no timeout by default in async-await. There is the await-timeout package, for example, that is adding a timeout behavior. Example:

import Timeout from 'await-timeout';
 
const timer = new Timeout();
try {
  await Promise.race([
    fetch('https://example.com'),
    timer.set(1000, 'Timeout!')
  ]);
} finally {
  timer.clear();
}

Taken from the docs: https://www.npmjs.com/package/await-timeout

As you can see, a Timeout is instantiated and its set method defines the timeout and the timeout message.

Lajos Arpad
  • 64,414
  • 37
  • 100
  • 175
1

Answer: No, there is not a top-level timeout on an await.

This feature is actually being used in Deno for a webserver for example:

import { serve } from "https://deno.land/std@0.103.0/http/server.ts";

const server = serve({ port: 8080 });
console.log(`HTTP webserver running.  Access it at:  http://localhost:8080/`);

console.log("A");

for await (const request of server) {
  let bodyContent = "Your user-agent is:\n\n";
  bodyContent += request.headers.get("user-agent") || "Unknown";

  request.respond({ status: 200, body: bodyContent });
}

console.log("B");

In this example, "A" gets printed in the console and "B" isn't until the webserver is shut down (which doesn't automatically happen).

guidsdo
  • 432
  • 2
  • 9