gecko-dev/testing/marionette/sync.js
Andreas Tolfsen 74fbed7467 Bug 1404946 - Have PollPromise accept an options dictionary. r=whimboo
This patch moves the "timeout" and "interval" positional arguments
on PollPromise to an options dictionary.

In the following code example it is hard to know which argument means
what because they are not named:

	new PollPromise(resolve => resolve(), 100, 100);

Named arguments can be achieved in JavaScript using option
dictionaries, and this patch changes the input PollPromise takes so
that the above example looks like this:

	new PollPromise(resolve => resolve(), {timeout: 100, interval: 100};

This plays especially well with code in testing/marionette/element.js
as we already have named arguments that we can pass directly in through
an object literal, making the code more readable and more compact:

	let timeout = 42;
	new PollPromise(resolve => resolve(), {timeout});

MozReview-Commit-ID: GFWNGQAeWk1

--HG--
extra : rebase_source : 296843053e72bd07463a3018b06782f0dcc4828a
2017-10-02 17:13:57 +01:00

176 lines
5.5 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const {
error,
TimeoutError,
} = Cu.import("chrome://marionette/content/error.js", {});
/* exported PollPromise, TimedPromise */
this.EXPORTED_SYMBOLS = ["PollPromise", "TimedPromise"];
const {TYPE_ONE_SHOT, TYPE_REPEATING_SLACK} = Ci.nsITimer;
/**
* @callback Condition
*
* @param {function(*)} resolve
* To be called when the condition has been met. Will return the
* resolved value.
* @param {function} reject
* To be called when the condition has not been met. Will cause
* the condition to be revaluated or time out.
*
* @return {*}
* The value from calling <code>resolve</code>.
*/
/**
* Runs a promise-like function off the main thread until it is resolved
* through <code>resolve</code> or <code>rejected</code> callbacks.
* The function is guaranteed to be run at least once, irregardless of
* the timeout.
*
* The <var>func</var> is evaluated every <var>interval</var> for as
* long as its runtime duration does not exceed <var>interval</var>.
* Evaluations occur sequentially, meaning that evaluations of
* <var>func</var> are queued if the runtime evaluation duration of
* <var>func</var> is greater than <var>interval</var>.
*
* <var>func</var> is given two arguments, <code>resolve</code> and
* <code>reject</code>, of which one must be called for the evaluation
* to complete. Calling <code>resolve</code> with an argument
* indicates that the expected wait condition was met and will return
* the passed value to the caller. Conversely, calling
* <code>reject</code> will evaluate <var>func</var> again until
* the <var>timeout</var> duration has elapsed or <var>func</var>
* throws. The passed value to <code>reject</code> will also be
* returned to the caller once the wait has expired.
*
* Usage:
*
* <pre><code>
* let els = new PollPromise((resolve, reject) => {
* let res = document.querySelectorAll("p");
* if (res.length > 0) {
* resolve(Array.from(res));
* } else {
* reject([]);
* }
* });
* </pre></code>
*
* @param {Condition} func
* Function to run off the main thread.
* @param {number=} [timeout=2000] timeout
* Desired timeout. If 0 or less than the runtime evaluation
* time of <var>func</var>, <var>func</var> is guaranteed to run
* at least once. The default is 2000 milliseconds.
* @param {number=} [interval=10] interval
* Duration between each poll of <var>func</var> in milliseconds.
* Defaults to 10 milliseconds.
*
* @return {Promise.<*>}
* Yields the value passed to <var>func</var>'s
* <code>resolve</code> or <code>reject</code> callbacks.
*
* @throws {*}
* If <var>func</var> throws, its error is propagated.
*/
function PollPromise(func, {timeout = 2000, interval = 10} = {}) {
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
return new Promise((resolve, reject) => {
const start = new Date().getTime();
const end = start + timeout;
let evalFn = () => {
new Promise(func).then(resolve, rejected => {
if (error.isError(rejected)) {
throw rejected;
}
// return if timeout is 0, allowing |func| to be evaluated at
// least once
if (start == end || new Date().getTime() >= end) {
resolve(rejected);
}
}).catch(reject);
};
// the repeating slack timer waits |interval|
// before invoking |evalFn|
evalFn();
timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
}).then(res => {
timer.cancel();
return res;
}, err => {
timer.cancel();
throw err;
});
}
/**
* The <code>TimedPromise</code> object represents the timed, eventual
* completion (or failure) of an asynchronous operation, and its
* resulting value.
*
* In contrast to a regular {@link Promise}, it times out after
* <var>timeout</var>.
*
* @param {Condition} func
* Function to run, which will have its <code>reject</code>
* callback invoked after the <var>timeout</var> duration is reached.
* It is given two callbacks: <code>resolve(value)</code> and
* <code>reject(error)</code>.
* @param {timeout=} [timeout=1500] timeout
* <var>condition</var>'s <code>reject</code> callback will be called
* after this timeout.
* @param {Error=} [throws=TimeoutError] throws
* When the <var>timeout</var> is hit, this error class will be
* thrown. If it is null, no error is thrown and the promise is
* instead resolved on timeout.
*
* @return {Promise.<*>}
* Timed promise.
*/
function TimedPromise(fn, {timeout = 1500, throws = TimeoutError} = {}) {
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
return new Promise((resolve, reject) => {
// Reject only if |throws| is given. Otherwise it is assumed that
// the user is OK with the promise timing out.
let bail = () => {
if (throws !== null) {
let err = new throws();
reject(err);
} else {
resolve();
}
};
timer.initWithCallback({notify: bail}, timeout, TYPE_ONE_SHOT);
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}).then(res => {
timer.cancel();
return res;
}, err => {
timer.cancel();
throw err;
});
}