DeferredTask.jsm

Firefox 28 note

Interface was changed in Firefox 28, and old methods were removed.

The DeferredTask.jsm JavaScript code module offers utility routines for a task that will run after a delay. Multiple attempts to run the same task before the delay will be coalesced. To use it, you first need to import the code module into your JavaScript scope:

Components.utils.import("resource://gre/modules/DeferredTask.jsm");

Use this, for instance, if you write data to a file and you expect that you may have to rewrite data very soon afterwards. With DeferredTask, the task is delayed by a few milliseconds and, should a new change to the data occur during this period,

  • only the final version of the data is actually written;
  • a further grace delay is added to take into account other changes.

The DeferredTask constructor

Requires Gecko 18.0(Firefox 18.0 / Thunderbird 18.0 / SeaMonkey 2.15)

If you have a function call you want to defer for two seconds, you can do so using the DeferredTask constructor, like this:

var task = new DeferredTask(myFunction, 2000);

You can also pass a generator function as the first argument of constructor.

Method overview

bool isPending();Obsolete since Gecko 28
void start();Obsolete since Gecko 28
void flush();Obsolete since Gecko 28
void cancel();Obsolete since Gecko 28
void arm();
void disarm();
Promise finalize();

Attributes

isArmed boolean Indicates whether the task is currently requested to start again later, regardless of whether it is currently running.
isRunning boolean Indicates whether the task is currently running. This is always true when read from code inside the task function, but can also be true when read from external code, in case the task is an asynchronous generator function.

Methods

isPending

Obsolete since Gecko 28

Check the current task state.

bool isPending();
Return value

Returns true if pending, false otherwise.

start

Obsolete since Gecko 28

Start (or postpone) task.

void start();

flush

Obsolete since Gecko 28

Perform any postponed task immediately.

void flush();

cancel

Obsolete since Gecko 28

Cancel any pending task.

void cancel();

arm

Request the execution of the task after the delay specified on construction. Multiple calls don't introduce further delays. If the task is running, the delay will start when the current execution finishes.

The task will always be executed on a different tick of the event loop, even if the delay specified on construction is zero. Multiple "arm" calls within the same tick of the event loop are guaranteed to result in a single execution of the task.

By design, this method doesn't provide a way for the caller to detect when the next execution terminates, or collect a result. In fact, doing that would often result in duplicate processing or logging. If a special operation or error logging is needed on completion, it can be better handled from within the task itself, for example using a try/catch/finally clause in the task. The "finalize" method can be used in the common case of waiting for completion on shutdown.

void arm();

disarm

Cancel any request for a delayed execution of the task, though the task itself cannot be canceled in case it is already running.

This method stops any currently running timer, thus the delay will restart from its original value in case the "arm" method is called again.

void disarm();

finalize

Ensure that any pending task is executed from start to finish, while preventing any attempt to arm the timer again.

  • If the task is running and the timer is armed, then one last execution from start to finish will happen again, immediately after the current execution terminates; then the returned promise will be resolved.
  • If the task is running and the timer is not armed, the returned promise will be resolved when the current execution terminates.
  • If the task is not running and the timer is armed, then the task is started immediately, and the returned promise resolves when the new execution terminates.
  • If the task is not running and the timer is not armed, the method returns a resolved promise.
Promise finalize();

Example

Firefox 28 note

This section describes about DeferredTask.jsm in Firefox 28.0 or higher.

Set up a function or an asynchronous task whose execution can be triggered after a defined delay. Multiple attempts to run the task before the delay has passed are coalesced. The task cannot be re-entered while running, but can be executed again after a previous run finished.

A common use case occurs when a data structure should be saved into a file every time the data changes, using asynchronous calls, and multiple changes to the data may happen within a short time:

let saveDeferredTask = new DeferredTask(function* () {
  yield OS.File.writeAtomic(...);
  // Any uncaught exception will be reported.
}, 2000);

// The task is ready, but will not be executed until requested.

The "arm" method can be used to start the internal timer that will result in the eventual execution of the task. Multiple attempts to arm the timer don't introduce further delays:

saveDeferredTask.arm();

// The task will be executed in 2 seconds from now.

yield waitOneSecond();
saveDeferredTask.arm();

// The task will be executed in 1 second from now.

The timer can be disarmed to reset the delay, or just to cancel execution:

saveDeferredTask.disarm();
saveDeferredTask.arm();

// The task will be executed in 2 seconds from now.

When the internal timer fires and the execution of the task starts, the task cannot be canceled anymore. It is, however, possible to arm the timer again during the execution of the task, in which case the task will need to finish before the timer is started again, thus guaranteeing a time of inactivity between executions that is at least equal to the provided delay.

The "finalize" method can be used to ensure that the task terminates properly. The promise it returns is resolved only after the last execution of the task is finished. To guarantee that the task is executed for the last time, the method prevents any attempt to arm the timer again.

If the timer is already armed when the "finalize" method is called, then the task is executed immediately. If the task was already running at this point, then one last execution from start to finish will happen again, immediately after the current execution terminates. If the timer is not armed, the "finalize" method only ensures that any running task terminates.

For example, during shutdown, you may want to ensure that any pending write is processed, using the latest version of the data if the timer is armed:

AsyncShutdown.profileBeforeChange.addBlocker(
  "Example service: shutting down",
  () => saveDeferredTask.finalize()
);

Instead, if you are going to delete the saved data from disk anyways, you might as well prevent any pending write from starting, while still ensuring that any write that is currently in progress terminates, so that the file is not in use any more:

saveDeferredTask.disarm();
saveDeferredTask.finalize().then(() => OS.File.remove(...))
                           .then(null, Components.utils.reportError);