Sume waitForJob in TypeScript: ms timeout, failed jobs resolve

waitForJob in @sume-com/sdk takes milliseconds, resolves for failed and canceled jobs, and throws only on timeout or a failed read. 25-line sample.

4 min readSume
All posts

waitForJob from @sume-com/sdk resolves with the job whenever it reaches any terminal status, including failed and canceled, so you must check job.status yourself. It throws only for two reasons: SumeJobTimeoutError when the deadline passes, and SumeJobRequestError when reading the job fails.

Its timeout and pollInterval are in milliseconds. The defaults I read in the SDK source (version 0.2.0) are 20 minutes and 2,000 ms, and the SDK runs page documents the helper.

A sample that handles every outcome

The program submits a video in async mode with an idempotency key, then waits. It typechecked against the SDK source in the Sume repository with only the process type missing, which the Node types add.

import { createSumeClient, generateVideoV1, waitForJob,
  SumeJobRequestError, SumeJobTimeoutError } from "@sume-com/sdk";

const client = createSumeClient({ apiKey: process.env.SUME_API_KEY! });
const { data: sub, error } = await generateVideoV1({
  client,
  headers: { "idempotency-key": "promo-clip-order-8823-v1" },
  body: { prompt: "Slow push-in on a ceramic mug", mode: "async" },
});
if (error) throw new Error(JSON.stringify(error));
const jobId = sub!.data.request_id;

try {
  const job = await waitForJob(jobId, {
    client,
    timeout: 10 * 60_000,   // milliseconds
    pollInterval: 3_000,    // milliseconds, a floor
  });
  if (job.status === "completed") console.log(job.result);
  else console.error(job.status, job.error);      // failed or canceled resolves too
} catch (e) {
  if (e instanceof SumeJobTimeoutError) console.warn("still running:", e.jobId, e.lastStatus);
  else if (e instanceof SumeJobRequestError) console.error("read failed:", e.jobId, e.status);
  else throw e;
}

How the loop behaves

waitForJob behavior, SDK source 0.2.0 and docs read 2026-10-10
SituationResult
Job completesResolves with the job; result holds the artifacts
Job fails or is canceledResolves too; read status and error
Deadline passesThrows SumeJobTimeoutError with jobId and lastStatus; the job is not canceled
A read returns a non-success responseThrows SumeJobRequestError with jobId, status and body
Server sends next_poll_after_secondsRaises the gap; it never makes it shorter than pollInterval

Two details that save a day

The deadline is checked before each sleep, so a short timeout cannot be overshot by a long server hint. And the generated operations such as generateVideoV1 do not throw: they resolve to { data, error, response }, which is why the sample checks error right after submitting.

When a timeout fires, log the job id and lastStatus, then decide whether to keep waiting. Because the job still runs, resubmitting the same prompt would pay twice. See the timeouts comparison for the other waits.

Choosing timeout and pollInterval

Pick the timeout from the work, not from habit. A short image job fits a one or two minute budget, while video work deserves the ten minutes in the sample. Keep pollInterval at or above the 2,000 ms default unless you have a reason; the server hint will raise it when it wants you to slow down, and each poll counts against your read bucket.

If you run many jobs at once, share one client and wait on each id in its own promise. Concurrency is limited by your plan, so submit in waves that respect it, as the admission docs describe, rather than starting fifty waits for jobs that sit queued.

  • Timeout and interval are milliseconds; write them as 10 * 60_000 and 3_000.
  • Branch on job.status after the call returns.
  • Catch the two error classes by instanceof, and rethrow anything else.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume