Search Docs
Loading...
Skip to content

Cancel Operations

Stop an export, a load or a save that your server no longer needs, or give it a deadline, with a standard AbortSignal.

8 mins
estimated time
Download
StackBlitz
GitHub

Exports, loads, downloads and saves go on in the engine after the call returns. When a client disconnects or a job runs out of time, your server no longer needs that work. Each engine call that exports, loads, downloads or saves takes an abortSignal option. An abort ends the call at once, stops its work in the engine and frees what it holds. On the server you cancel in code, because there is no user interface.

This guide covers how to cancel a call, which calls you can cancel, how to give a call a deadline, how to cancel from a progress callback, and what the engine keeps after a cancel.

Cancelling a Call#

We start with a page that holds an image block. The image still downloads when the export starts, so the export waits for it, and the abort has work to stop.

const scene = engine.scene.create();
const page = engine.block.create('page');
engine.block.setWidth(page, 800);
engine.block.setHeight(page, 600);
engine.block.appendChild(scene, page);
// An image fill instead of addImage(), because addImage() waits for the download.
// An export with nothing to wait for ends inside its call, before the abort.
const imageBlock = engine.block.create('graphic');
engine.block.setShape(imageBlock, engine.block.createShape('rect'));
const imageFill = engine.block.createFill('image');
engine.block.setString(imageFill, 'fill/image/imageFileURI', IMAGE_URL);
engine.block.setFill(imageBlock, imageFill);
engine.block.setWidth(imageBlock, 800);
engine.block.setHeight(imageBlock, 600);
engine.block.appendChild(page, imageBlock);

Create an AbortController and pass its signal as abortSignal in the options of the call. Call controller.abort() to cancel. The promise rejects with Error('AbortSignal received').

const controller = new AbortController();
const exporting = engine.block.export(page, {
mimeType: 'image/png',
abortSignal: controller.signal
});
controller.abort();
try {
await exporting;
console.log('The export ended before the abort');
} catch (error) {
// Logs "AbortSignal received"
console.log('The export stopped:', (error as Error).message);
}

A signal that aborted before the call rejects at once, and the call starts nothing. A call that has already settled ignores the abort. An export with nothing to load can end inside its call, so an abort on the next line comes too late, and the promise resolves with the file.

Calls That Can Be Cancelled#

Each of these calls takes abortSignal in its options object, which is its last argument. In engine.scene.createFromImage() the options object is the seventh argument, after spacingInScreenSpace. In engine.block.saveToString() it is the fourth, after onDisallowedResourceScheme.

  • Exports: engine.block.export() (images and PDF), engine.block.exportWithColorMask(), engine.block.exportVideo(), engine.block.exportAudio()
  • Scene and block loads: engine.scene.load(), engine.scene.loadFromURL(), engine.scene.loadFromString() with waitForResources, engine.scene.loadFromArchiveURL(), engine.scene.applyTemplateFromURL(), engine.block.loadFromURL(), engine.block.loadFromArchiveURL()
  • New scenes from media: engine.scene.createFromImage(), engine.scene.createFromVideo()
  • Downloads for a block or an asset: engine.block.createCaptionsFromURI(), engine.block.addImageFileURIToSourceSet(), engine.block.addVideoFileURIToSourceSet(), engine.block.forceLoadAVResource(), engine.block.forceLoadResources(), engine.asset.addLocalAssetSourceFromJSONURI(), engine.asset.apply(), engine.asset.defaultApplyAsset()
  • Lookups: engine.editor.getMimeType(), engine.editor.getFontMetrics(), engine.block.getDominantColors()
  • Color profiles: engine.scene.setCMYKProfile(), engine.editor.loadCMYKProfile()
  • Saves: engine.scene.saveToArchive(), engine.block.saveToArchive(), and engine.scene.saveToString() or engine.block.saveToString() while they wait for onDisallowedResourceScheme

@cesdk/node has no video export, so its exportVideo() throws. The native package @cesdk/node-native exports video, and its exportVideo() takes abortSignal too.

Giving a Call a Deadline#

Pass AbortSignal.timeout(ms) as abortSignal. The engine has no timeout of its own, so the signal is the deadline. Here we give the scene load ten seconds.

const deadline = AbortSignal.timeout(10_000);
try {
await engine.scene.load(SCENE_URL, { abortSignal: deadline });
console.log('The scene loaded in time');
} catch (error) {
if (!deadline.aborted) {
throw error;
}
console.log('The scene did not load within 10 seconds');
}

A deadline rejects with the same AbortSignal received error as abort(). Check aborted on the signal to tell a missed deadline from a failed download, and throw every other error again.

Cancelling From a Progress Callback#

You can call controller.abort() inside onProgress of exportAudio(). @cesdk/node has no video export, so we show the cancel with a WAV export of a page that holds audio. Here we stop an export that has rendered less than half of its frames after one minute.

const controller = new AbortController();
const startedAt = Date.now();
try {
const blob = await engine.block.exportAudio(page, {
mimeType: 'audio/wav',
abortSignal: controller.signal,
onProgress: (renderedFrames, encodedFrames, totalFrames) => {
const elapsed = Date.now() - startedAt;
if (elapsed > 60_000 && renderedFrames < totalFrames / 2) {
controller.abort();
}
}
});
console.log(`The WAV file has ${blob.size} bytes`);
} catch (error) {
if (!controller.signal.aborted) {
throw error;
}
console.log('The export was too slow and stopped');
}

No progress report follows the abort, and the next export can start at once. The time of the first reports includes the start-up of the export, so a projection of the total time from them would stop fast exports too.

What a Cancel Stops and What It Keeps#

A cancel stops the work of the call, but the engine keeps what other blocks and calls still need:

  • A video export and an audio export that mixes a page stop at once and release their encoders.
  • An image or PDF export frees its document in the next engine update.
  • An audio export that copies the audio of one block without the mixer finishes the copy in the background and drops it.
  • A load stops its download, unless a block or another call needs the same file. The engine reads every video fill at each update, so the download of a video fill goes on.
  • load() and loadFromURL() keep the current scene. With waitForResources the new scene replaces it before the wait for its resources, so a cancel during that wait keeps the new scene. loadFromString() with waitForResources loads the scene at once, so a cancel ends only the wait.
  • loadCMYKProfile() ends only the wait, because the document keeps its profile loaded.
  • A cancelled setCMYKProfile() gives back its claim, so an assignment that it replaced still takes effect.
  • An answer of onDisallowedResourceScheme after the abort changes nothing.
  • asset.apply() stops the download that the default apply waits for, so it creates no block. An asset source with its own applyAsset keeps running in your code. A block that it creates stays, and the call does not return it.
  • getDominantColors() waits while a resource of the block loads, so a signal gives it a deadline.

Starting the Next Call#

After the promise rejects, the engine is free for the next export or load. Start it in the rejection handler or after the await. Here we export the same page again, this time without a signal.

const blob = await engine.block.export(page, { mimeType: 'image/png' });
console.log(`The next export made a PNG file of ${blob.size} bytes`);

The second export waits for the image that the first export waited for, because the image block still needs it.

An audio export that starts inside the progress callback of another audio export fails with the ENCODE.AUDIO_CONCURRENT_ENCODING error. Start it after the running export settles.

Troubleshooting#

  • The promise rejects with AbortSignal received at once: The signal aborted before the call. Create a new AbortController for each call.
  • The promise resolves although you aborted: The call ended before the abort. An export with nothing to load can end inside its call.
  • The download of a cancelled load goes on: Another block or another call needs the same file.
  • An audio export fails with ENCODE.AUDIO_CONCURRENT_ENCODING: It started inside the progress callback of a running audio export.
  • A call rejects with a validation error for abortSignal: The option must be an AbortSignal, not a boolean or a string.

API Reference#

These are the methods this guide calls. The section Calls That Can Be Cancelled lists every method that takes abortSignal.

Method Category Purpose
engine.block.export() Block Export a block as an image or a PDF
engine.block.exportAudio() Block Export the audio of a block
engine.scene.load() Scene Load a scene or an archive from a URL or a string

Next Steps#