blob: 66892c1ec365e002f9200251860aa5edb3b25fc7 [file] [log] [blame]
<!DOCTYPE html>
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<link rel="import" href="/base/raf.html">
<script>
'use strict';
tr.exportTo('tr.b', function() {
/**
* A task is a combination of a run callback, a set of subtasks, and an after
* task.
*
* When executed, a task does the following things:
* 1. Runs its callback
* 2. Runs its subtasks
* 3. Runs its after callback.
*
* The list of subtasks and after task can be mutated inside step #1 but as
* soon as the task's callback returns, the subtask list and after task is
* fixed and cannot be changed again.
*
* Use task.after().after().after() to describe the toplevel passes that make
* up your computation. Then, use subTasks to add detail to each subtask as it
* runs. For example:
* var pieces = [];
* taskA = new Task(function() { pieces = getPieces(); });
* taskA.after(function(taskA) {
* pieces.forEach(function(piece) {
* taskA.subTask(function(taskB) { piece.process(); }, this);
* });
* });
*
* @constructor
*/
function Task(runCb, thisArg) {
if (runCb !== undefined && thisArg === undefined)
throw new Error('Almost certainly, you meant to pass a thisArg.');
this.runCb_ = runCb;
this.thisArg_ = thisArg;
this.afterTask_ = undefined;
this.subTasks_ = [];
}
Task.prototype = {
/*
* See constructor documentation on semantics of subtasks.
*/
subTask: function(cb, thisArg) {
if (cb instanceof Task)
this.subTasks_.push(cb);
else
this.subTasks_.push(new Task(cb, thisArg));
return this.subTasks_[this.subTasks_.length - 1];
},
/**
* Runs the current task and returns the task that should be executed next.
*/
run: function() {
if (this.runCb_ !== undefined)
this.runCb_.call(this.thisArg_, this);
var subTasks = this.subTasks_;
this.subTasks_ = undefined; // Prevent more subTasks from being posted.
if (!subTasks.length)
return this.afterTask_;
// If there are subtasks, then we want to execute all the subtasks and
// then this task's afterTask. To make this happen, we update the
// afterTask of all the subtasks so the point upward to each other, e.g.
// subTask[0].afterTask to subTask[1] and so on. Then, the last subTask's
// afterTask points at this task's afterTask.
for (var i = 1; i < subTasks.length; i++)
subTasks[i - 1].afterTask_ = subTasks[i];
subTasks[subTasks.length - 1].afterTask_ = this.afterTask_;
return subTasks[0];
},
/*
* See constructor documentation on semantics of after tasks.
*/
after: function(cb, thisArg) {
if (this.afterTask_)
throw new Error('Has an after task already');
if (cb instanceof Task)
this.afterTask_ = cb;
else
this.afterTask_ = new Task(cb, thisArg);
return this.afterTask_;
},
/*
* Adds a task after the chain of tasks.
*/
enqueue: function(cb, thisArg) {
var lastTask = this;
while (lastTask.afterTask_)
lastTask = lastTask.afterTask_;
return lastTask.after(cb, thisArg);
}
};
Task.RunSynchronously = function(task) {
var curTask = task;
while (curTask)
curTask = curTask.run();
}
/**
* Runs a task using raf.requestIdleCallback, returning
* a promise for its completion.
*/
Task.RunWhenIdle = function(task) {
return new Promise(function(resolve, reject) {
var curTask = task;
function runAnother() {
try {
curTask = curTask.run();
} catch (e) {
reject(e);
console.error(e.stack);
return;
}
if (curTask) {
tr.b.requestIdleCallback(runAnother);
return;
}
resolve();
}
tr.b.requestIdleCallback(runAnother);
});
}
return {
Task: Task
};
});
</script>