Permalink
Find file Copy path
f6d95e9 Aug 24, 2018
1 contributor

Users who have contributed to this file

1146 lines (1028 sloc) 31.4 KB
/* globals setImmediate, clearImmediate, setTimeout, clearTimeout */
/* jshint -W083, -W098, -W003 */
"use strict";
var Aff = function () {
// A unique value for empty.
var EMPTY = {};
/*
An awkward approximation. We elide evidence we would otherwise need in PS for
efficiency sake.
data Aff eff a
= Pure a
| Throw Error
| Catch (Aff eff a) (Error -> Aff eff a)
| Sync (Eff eff a)
| Async ((Either Error a -> Eff eff Unit) -> Eff eff (Canceler eff))
| forall b. Bind (Aff eff b) (b -> Aff eff a)
| forall b. Bracket (Aff eff b) (BracketConditions eff b) (b -> Aff eff a)
| forall b. Fork Boolean (Aff eff b) ?(Fiber eff b -> a)
| Sequential (ParAff aff a)
*/
var PURE = "Pure";
var THROW = "Throw";
var CATCH = "Catch";
var SYNC = "Sync";
var ASYNC = "Async";
var BIND = "Bind";
var BRACKET = "Bracket";
var FORK = "Fork";
var SEQ = "Sequential";
/*
data ParAff eff a
= forall b. Map (b -> a) (ParAff eff b)
| forall b. Apply (ParAff eff (b -> a)) (ParAff eff b)
| Alt (ParAff eff a) (ParAff eff a)
| ?Par (Aff eff a)
*/
var MAP = "Map";
var APPLY = "Apply";
var ALT = "Alt";
// Various constructors used in interpretation
var CONS = "Cons"; // Cons-list, for stacks
var RESUME = "Resume"; // Continue indiscriminately
var RELEASE = "Release"; // Continue with bracket finalizers
var FINALIZER = "Finalizer"; // A non-interruptible effect
var FINALIZED = "Finalized"; // Marker for finalization
var FORKED = "Forked"; // Reference to a forked fiber, with resumption stack
var FIBER = "Fiber"; // Actual fiber reference
var THUNK = "Thunk"; // Primed effect, ready to invoke
function Aff(tag, _1, _2, _3) {
this.tag = tag;
this._1 = _1;
this._2 = _2;
this._3 = _3;
}
function AffCtr(tag) {
var fn = function (_1, _2, _3) {
return new Aff(tag, _1, _2, _3);
};
fn.tag = tag;
return fn;
}
function nonCanceler(error) {
return new Aff(PURE, void 0);
}
function runEff(eff) {
try {
eff();
} catch (error) {
setTimeout(function () {
throw error;
}, 0);
}
}
function runSync(left, right, eff) {
try {
return right(eff());
} catch (error) {
return left(error);
}
}
function runAsync(left, eff, k) {
try {
return eff(k)();
} catch (error) {
k(left(error))();
return nonCanceler;
}
}
var Scheduler = function () {
var limit = 1024;
var size = 0;
var ix = 0;
var queue = new Array(limit);
var draining = false;
function drain() {
var thunk;
draining = true;
while (size !== 0) {
size--;
thunk = queue[ix];
queue[ix] = void 0;
ix = (ix + 1) % limit;
thunk();
}
draining = false;
}
return {
isDraining: function () {
return draining;
},
enqueue: function (cb) {
var i, tmp;
if (size === limit) {
tmp = draining;
drain();
draining = tmp;
}
queue[(ix + size) % limit] = cb;
size++;
if (!draining) {
drain();
}
}
};
}();
function Supervisor(util) {
var fibers = {};
var fiberId = 0;
var count = 0;
return {
register: function (fiber) {
var fid = fiberId++;
fiber.onComplete({
rethrow: true,
handler: function (result) {
return function () {
count--;
delete fibers[fid];
};
}
});
fibers[fid] = fiber;
count++;
},
isEmpty: function () {
return count === 0;
},
killAll: function (killError, cb) {
return function () {
var killCount = 0;
var kills = {};
function kill(fid) {
kills[fid] = fibers[fid].kill(killError, function (result) {
return function () {
delete kills[fid];
killCount--;
if (util.isLeft(result) && util.fromLeft(result)) {
setTimeout(function () {
throw util.fromLeft(result);
}, 0);
}
if (killCount === 0) {
cb();
}
};
})();
}
for (var k in fibers) {
if (fibers.hasOwnProperty(k)) {
killCount++;
kill(k);
}
}
fibers = {};
fiberId = 0;
count = 0;
return function (error) {
return new Aff(SYNC, function () {
for (var k in kills) {
if (kills.hasOwnProperty(k)) {
kills[k]();
}
}
});
};
};
}
};
}
// Fiber state machine
var SUSPENDED = 0; // Suspended, pending a join.
var CONTINUE = 1; // Interpret the next instruction.
var STEP_BIND = 2; // Apply the next bind.
var STEP_RESULT = 3; // Handle potential failure from a result.
var PENDING = 4; // An async effect is running.
var RETURN = 5; // The current stack has returned.
var COMPLETED = 6; // The entire fiber has completed.
function Fiber(util, supervisor, aff) {
// Monotonically increasing tick, increased on each asynchronous turn.
var runTick = 0;
// The current branch of the state machine.
var status = SUSPENDED;
// The current point of interest for the state machine branch.
var step = aff; // Successful step
var fail = null; // Failure step
var interrupt = null; // Asynchronous interrupt
// Stack of continuations for the current fiber.
var bhead = null;
var btail = null;
// Stack of attempts and finalizers for error recovery. Every `Cons` is also
// tagged with current `interrupt` state. We use this to track which items
// should be ignored or evaluated as a result of a kill.
var attempts = null;
// A special state is needed for Bracket, because it cannot be killed. When
// we enter a bracket acquisition or finalizer, we increment the counter,
// and then decrement once complete.
var bracketCount = 0;
// Each join gets a new id so they can be revoked.
var joinId = 0;
var joins = null;
var rethrow = true;
// Each invocation of `run` requires a tick. When an asynchronous effect is
// resolved, we must check that the local tick coincides with the fiber
// tick before resuming. This prevents multiple async continuations from
// accidentally resuming the same fiber. A common example may be invoking
// the provided callback in `makeAff` more than once, but it may also be an
// async effect resuming after the fiber was already cancelled.
function run(localRunTick) {
var tmp, result, attempt;
while (true) {
tmp = null;
result = null;
attempt = null;
switch (status) {
case STEP_BIND:
status = CONTINUE;
step = bhead(step);
if (btail === null) {
bhead = null;
} else {
bhead = btail._1;
btail = btail._2;
}
break;
case STEP_RESULT:
if (util.isLeft(step)) {
status = RETURN;
fail = step;
step = null;
} else if (bhead === null) {
status = RETURN;
} else {
status = STEP_BIND;
step = util.fromRight(step);
}
break;
case CONTINUE:
switch (step.tag) {
case BIND:
if (bhead) {
btail = new Aff(CONS, bhead, btail);
}
bhead = step._2;
status = CONTINUE;
step = step._1;
break;
case PURE:
if (bhead === null) {
status = RETURN;
step = util.right(step._1);
} else {
status = STEP_BIND;
step = step._1;
}
break;
case SYNC:
status = STEP_RESULT;
step = runSync(util.left, util.right, step._1);
break;
case ASYNC:
status = PENDING;
step = runAsync(util.left, step._1, function (result) {
return function () {
if (runTick !== localRunTick) {
return;
}
runTick++;
Scheduler.enqueue(function () {
// It's possible to interrupt the fiber between enqueuing and
// resuming, so we need to check that the runTick is still
// valid.
if (runTick !== localRunTick + 1) {
return;
}
status = STEP_RESULT;
step = result;
run(runTick);
});
};
});
return;
case THROW:
status = RETURN;
fail = util.left(step._1);
step = null;
break;
// Enqueue the Catch so that we can call the error handler later on
// in case of an exception.
case CATCH:
if (bhead === null) {
attempts = new Aff(CONS, step, attempts, interrupt);
} else {
attempts = new Aff(CONS, step, new Aff(CONS, new Aff(RESUME, bhead, btail), attempts, interrupt), interrupt);
}
bhead = null;
btail = null;
status = CONTINUE;
step = step._1;
break;
// Enqueue the Bracket so that we can call the appropriate handlers
// after resource acquisition.
case BRACKET:
bracketCount++;
if (bhead === null) {
attempts = new Aff(CONS, step, attempts, interrupt);
} else {
attempts = new Aff(CONS, step, new Aff(CONS, new Aff(RESUME, bhead, btail), attempts, interrupt), interrupt);
}
bhead = null;
btail = null;
status = CONTINUE;
step = step._1;
break;
case FORK:
status = STEP_RESULT;
tmp = Fiber(util, supervisor, step._2);
if (supervisor) {
supervisor.register(tmp);
}
if (step._1) {
tmp.run();
}
step = util.right(tmp);
break;
case SEQ:
status = CONTINUE;
step = sequential(util, supervisor, step._1);
break;
}
break;
case RETURN:
bhead = null;
btail = null;
// If the current stack has returned, and we have no other stacks to
// resume or finalizers to run, the fiber has halted and we can
// invoke all join callbacks. Otherwise we need to resume.
if (attempts === null) {
status = COMPLETED;
step = interrupt || fail || step;
} else {
// The interrupt status for the enqueued item.
tmp = attempts._3;
attempt = attempts._1;
attempts = attempts._2;
switch (attempt.tag) {
// We cannot recover from an interrupt. Otherwise we should
// continue stepping, or run the exception handler if an exception
// was raised.
case CATCH:
// We should compare the interrupt status as well because we
// only want it to apply if there has been an interrupt since
// enqueuing the catch.
if (interrupt && interrupt !== tmp) {
status = RETURN;
} else if (fail) {
status = CONTINUE;
step = attempt._2(util.fromLeft(fail));
fail = null;
}
break;
// We cannot resume from an interrupt or exception.
case RESUME:
// As with Catch, we only want to ignore in the case of an
// interrupt since enqueing the item.
if (interrupt && interrupt !== tmp || fail) {
status = RETURN;
} else {
bhead = attempt._1;
btail = attempt._2;
status = STEP_BIND;
step = util.fromRight(step);
}
break;
// If we have a bracket, we should enqueue the handlers,
// and continue with the success branch only if the fiber has
// not been interrupted. If the bracket acquisition failed, we
// should not run either.
case BRACKET:
bracketCount--;
if (fail === null) {
result = util.fromRight(step);
// We need to enqueue the Release with the same interrupt
// status as the Bracket that is initiating it.
attempts = new Aff(CONS, new Aff(RELEASE, attempt._2, result), attempts, tmp);
// We should only coninue as long as the interrupt status has not changed or
// we are currently within a non-interruptable finalizer.
if (interrupt === tmp || bracketCount > 0) {
status = CONTINUE;
step = attempt._3(result);
}
}
break;
// Enqueue the appropriate handler. We increase the bracket count
// because it should not be cancelled.
case RELEASE:
bracketCount++;
attempts = new Aff(CONS, new Aff(FINALIZED, step, fail), attempts, interrupt);
status = CONTINUE;
// It has only been killed if the interrupt status has changed
// since we enqueued the item.
if (interrupt && interrupt !== tmp) {
step = attempt._1.killed(util.fromLeft(interrupt))(attempt._2);
} else if (fail) {
step = attempt._1.failed(util.fromLeft(fail))(attempt._2);
} else {
step = attempt._1.completed(util.fromRight(step))(attempt._2);
}
fail = null;
break;
case FINALIZER:
bracketCount++;
attempts = new Aff(CONS, new Aff(FINALIZED, step, fail), attempts, interrupt);
status = CONTINUE;
step = attempt._1;
break;
case FINALIZED:
bracketCount--;
status = RETURN;
step = attempt._1;
fail = attempt._2;
break;
}
}
break;
case COMPLETED:
for (var k in joins) {
if (joins.hasOwnProperty(k)) {
rethrow = rethrow && joins[k].rethrow;
runEff(joins[k].handler(step));
}
}
joins = null;
// If we have an interrupt and a fail, then the thread threw while
// running finalizers. This should always rethrow in a fresh stack.
if (interrupt && fail) {
setTimeout(function () {
throw util.fromLeft(fail);
}, 0);
// If we have an unhandled exception, and no other fiber has joined
// then we need to throw the exception in a fresh stack.
} else if (util.isLeft(step) && rethrow) {
setTimeout(function () {
// Guard on reathrow because a completely synchronous fiber can
// still have an observer which was added after-the-fact.
if (rethrow) {
throw util.fromLeft(step);
}
}, 0);
}
return;
case SUSPENDED:
status = CONTINUE;
break;
case PENDING: return;
}
}
}
function onComplete(join) {
return function () {
if (status === COMPLETED) {
rethrow = rethrow && join.rethrow;
join.handler(step)();
return function () {};
}
var jid = joinId++;
joins = joins || {};
joins[jid] = join;
return function() {
if (joins !== null) {
delete joins[jid];
}
};
};
}
function kill(error, cb) {
return function () {
if (status === COMPLETED) {
cb(util.right(void 0))();
return function () {};
}
var canceler = onComplete({
rethrow: false,
handler: function (/* unused */) {
return cb(util.right(void 0));
}
})();
switch (status) {
case SUSPENDED:
interrupt = util.left(error);
status = COMPLETED;
step = interrupt;
run(runTick);
break;
case PENDING:
if (interrupt === null) {
interrupt = util.left(error);
}
if (bracketCount === 0) {
if (status === PENDING) {
attempts = new Aff(CONS, new Aff(FINALIZER, step(error)), attempts, interrupt);
}
status = RETURN;
step = null;
fail = null;
run(++runTick);
}
break;
default:
if (interrupt === null) {
interrupt = util.left(error);
}
if (bracketCount === 0) {
status = RETURN;
step = null;
fail = null;
}
}
return canceler;
};
}
function join(cb) {
return function () {
var canceler = onComplete({
rethrow: false,
handler: cb
})();
if (status === SUSPENDED) {
run(runTick);
}
return canceler;
};
}
return {
kill: kill,
join: join,
onComplete: onComplete,
isSuspended: function () {
return status === SUSPENDED;
},
run: function () {
if (status === SUSPENDED) {
if (!Scheduler.isDraining()) {
Scheduler.enqueue(function () {
run(runTick);
});
} else {
run(runTick);
}
}
}
};
}
function runPar(util, supervisor, par, cb) {
// Table of all forked fibers.
var fiberId = 0;
var fibers = {};
// Table of currently running cancelers, as a product of `Alt` behavior.
var killId = 0;
var kills = {};
// Error used for early cancelation on Alt branches.
var early = new Error("[ParAff] Early exit");
// Error used to kill the entire tree.
var interrupt = null;
// The root pointer of the tree.
var root = EMPTY;
// Walks a tree, invoking all the cancelers. Returns the table of pending
// cancellation fibers.
function kill(error, par, cb) {
var step = par;
var head = null;
var tail = null;
var count = 0;
var kills = {};
var tmp, kid;
loop: while (true) {
tmp = null;
switch (step.tag) {
case FORKED:
if (step._3 === EMPTY) {
tmp = fibers[step._1];
kills[count++] = tmp.kill(error, function (result) {
return function () {
count--;
if (count === 0) {
cb(result)();
}
};
});
}
// Terminal case.
if (head === null) {
break loop;
}
// Go down the right side of the tree.
step = head._2;
if (tail === null) {
head = null;
} else {
head = tail._1;
tail = tail._2;
}
break;
case MAP:
step = step._2;
break;
case APPLY:
case ALT:
if (head) {
tail = new Aff(CONS, head, tail);
}
head = step;
step = step._1;
break;
}
}
if (count === 0) {
cb(util.right(void 0))();
} else {
// Run the cancelation effects. We alias `count` because it's mutable.
kid = 0;
tmp = count;
for (; kid < tmp; kid++) {
kills[kid] = kills[kid]();
}
}
return kills;
}
// When a fiber resolves, we need to bubble back up the tree with the
// result, computing the applicative nodes.
function join(result, head, tail) {
var fail, step, lhs, rhs, tmp, kid;
if (util.isLeft(result)) {
fail = result;
step = null;
} else {
step = result;
fail = null;
}
loop: while (true) {
lhs = null;
rhs = null;
tmp = null;
kid = null;
// We should never continue if the entire tree has been interrupted.
if (interrupt !== null) {
return;
}
// We've made it all the way to the root of the tree, which means
// the tree has fully evaluated.
if (head === null) {
cb(fail || step)();
return;
}
// The tree has already been computed, so we shouldn't try to do it
// again. This should never happen.
// TODO: Remove this?
if (head._3 !== EMPTY) {
return;
}
switch (head.tag) {
case MAP:
if (fail === null) {
head._3 = util.right(head._1(util.fromRight(step)));
step = head._3;
} else {
head._3 = fail;
}
break;
case APPLY:
lhs = head._1._3;
rhs = head._2._3;
// If we have a failure we should kill the other side because we
// can't possible yield a result anymore.
if (fail) {
head._3 = fail;
tmp = true;
kid = killId++;
kills[kid] = kill(early, fail === lhs ? head._2 : head._1, function (/* unused */) {
return function () {
delete kills[kid];
if (tmp) {
tmp = false;
} else if (tail === null) {
join(fail, null, null);
} else {
join(fail, tail._1, tail._2);
}
};
});
if (tmp) {
tmp = false;
return;
}
} else if (lhs === EMPTY || rhs === EMPTY) {
// We can only proceed if both sides have resolved.
return;
} else {
step = util.right(util.fromRight(lhs)(util.fromRight(rhs)));
head._3 = step;
}
break;
case ALT:
lhs = head._1._3;
rhs = head._2._3;
// We can only proceed if both have resolved or we have a success
if (lhs === EMPTY && util.isLeft(rhs) || rhs === EMPTY && util.isLeft(lhs)) {
return;
}
// If both sides resolve with an error, we should continue with the
// first error
if (lhs !== EMPTY && util.isLeft(lhs) && rhs !== EMPTY && util.isLeft(rhs)) {
fail = step === lhs ? rhs : lhs;
step = null;
head._3 = fail;
} else {
head._3 = step;
tmp = true;
kid = killId++;
// Once a side has resolved, we need to cancel the side that is still
// pending before we can continue.
kills[kid] = kill(early, step === lhs ? head._2 : head._1, function (/* unused */) {
return function () {
delete kills[kid];
if (tmp) {
tmp = false;
} else if (tail === null) {
join(step, null, null);
} else {
join(step, tail._1, tail._2);
}
};
});
if (tmp) {
tmp = false;
return;
}
}
break;
}
if (tail === null) {
head = null;
} else {
head = tail._1;
tail = tail._2;
}
}
}
function resolve(fiber) {
return function (result) {
return function () {
delete fibers[fiber._1];
fiber._3 = result;
join(result, fiber._2._1, fiber._2._2);
};
};
}
// Walks the applicative tree, substituting non-applicative nodes with
// `FORKED` nodes. In this tree, all applicative nodes use the `_3` slot
// as a mutable slot for memoization. In an unresolved state, the `_3`
// slot is `EMPTY`. In the cases of `ALT` and `APPLY`, we always walk
// the left side first, because both operations are left-associative. As
// we `RETURN` from those branches, we then walk the right side.
function run() {
var status = CONTINUE;
var step = par;
var head = null;
var tail = null;
var tmp, fid;
loop: while (true) {
tmp = null;
fid = null;
switch (status) {
case CONTINUE:
switch (step.tag) {
case MAP:
if (head) {
tail = new Aff(CONS, head, tail);
}
head = new Aff(MAP, step._1, EMPTY, EMPTY);
step = step._2;
break;
case APPLY:
if (head) {
tail = new Aff(CONS, head, tail);
}
head = new Aff(APPLY, EMPTY, step._2, EMPTY);
step = step._1;
break;
case ALT:
if (head) {
tail = new Aff(CONS, head, tail);
}
head = new Aff(ALT, EMPTY, step._2, EMPTY);
step = step._1;
break;
default:
// When we hit a leaf value, we suspend the stack in the `FORKED`.
// When the fiber resolves, it can bubble back up the tree.
fid = fiberId++;
status = RETURN;
tmp = step;
step = new Aff(FORKED, fid, new Aff(CONS, head, tail), EMPTY);
tmp = Fiber(util, supervisor, tmp);
tmp.onComplete({
rethrow: false,
handler: resolve(step)
})();
fibers[fid] = tmp;
if (supervisor) {
supervisor.register(tmp);
}
}
break;
case RETURN:
// Terminal case, we are back at the root.
if (head === null) {
break loop;
}
// If we are done with the right side, we need to continue down the
// left. Otherwise we should continue up the stack.
if (head._1 === EMPTY) {
head._1 = step;
status = CONTINUE;
step = head._2;
head._2 = EMPTY;
} else {
head._2 = step;
step = head;
if (tail === null) {
head = null;
} else {
head = tail._1;
tail = tail._2;
}
}
}
}
// Keep a reference to the tree root so it can be cancelled.
root = step;
for (fid = 0; fid < fiberId; fid++) {
fibers[fid].run();
}
}
// Cancels the entire tree. If there are already subtrees being canceled,
// we need to first cancel those joins. We will then add fresh joins for
// all pending branches including those that were in the process of being
// canceled.
function cancel(error, cb) {
interrupt = util.left(error);
var innerKills;
for (var kid in kills) {
if (kills.hasOwnProperty(kid)) {
innerKills = kills[kid];
for (kid in innerKills) {
if (innerKills.hasOwnProperty(kid)) {
innerKills[kid]();
}
}
}
}
kills = null;
var newKills = kill(error, root, cb);
return function (killError) {
return new Aff(ASYNC, function (killCb) {
return function () {
for (var kid in newKills) {
if (newKills.hasOwnProperty(kid)) {
newKills[kid]();
}
}
return nonCanceler;
};
});
};
}
run();
return function (killError) {
return new Aff(ASYNC, function (killCb) {
return function () {
return cancel(killError, killCb);
};
});
};
}
function sequential(util, supervisor, par) {
return new Aff(ASYNC, function (cb) {
return function () {
return runPar(util, supervisor, par, cb);
};
});
}
Aff.EMPTY = EMPTY;
Aff.Pure = AffCtr(PURE);
Aff.Throw = AffCtr(THROW);
Aff.Catch = AffCtr(CATCH);
Aff.Sync = AffCtr(SYNC);
Aff.Async = AffCtr(ASYNC);
Aff.Bind = AffCtr(BIND);
Aff.Bracket = AffCtr(BRACKET);
Aff.Fork = AffCtr(FORK);
Aff.Seq = AffCtr(SEQ);
Aff.ParMap = AffCtr(MAP);
Aff.ParApply = AffCtr(APPLY);
Aff.ParAlt = AffCtr(ALT);
Aff.Fiber = Fiber;
Aff.Supervisor = Supervisor;
Aff.Scheduler = Scheduler;
Aff.nonCanceler = nonCanceler;
return Aff;
}();
exports._pure = Aff.Pure;
exports._throwError = Aff.Throw;
exports._catchError = function (aff) {
return function (k) {
return Aff.Catch(aff, k);
};
};
exports._map = function (f) {
return function (aff) {
if (aff.tag === Aff.Pure.tag) {
return Aff.Pure(f(aff._1));
} else {
return Aff.Bind(aff, function (value) {
return Aff.Pure(f(value));
});
}
};
};
exports._bind = function (aff) {
return function (k) {
return Aff.Bind(aff, k);
};
};
exports._fork = function (immediate) {
return function (aff) {
return Aff.Fork(immediate, aff);
};
};
exports._liftEffect = Aff.Sync;
exports._parAffMap = function (f) {
return function (aff) {
return Aff.ParMap(f, aff);
};
};
exports._parAffApply = function (aff1) {
return function (aff2) {
return Aff.ParApply(aff1, aff2);
};
};
exports._parAffAlt = function (aff1) {
return function (aff2) {
return Aff.ParAlt(aff1, aff2);
};
};
exports.makeAff = Aff.Async;
exports.generalBracket = function (acquire) {
return function (options) {
return function (k) {
return Aff.Bracket(acquire, options, k);
};
};
};
exports._makeFiber = function (util, aff) {
return function () {
return Aff.Fiber(util, null, aff);
};
};
exports._makeSupervisedFiber = function (util, aff) {
return function () {
var supervisor = Aff.Supervisor(util);
return {
fiber: Aff.Fiber(util, supervisor, aff),
supervisor: supervisor
};
};
};
exports._killAll = function (error, supervisor, cb) {
return supervisor.killAll(error, cb);
};
exports._delay = function () {
function setDelay(n, k) {
if (n === 0 && typeof setImmediate !== "undefined") {
return setImmediate(k);
} else {
return setTimeout(k, n);
}
}
function clearDelay(n, t) {
if (n === 0 && typeof clearImmediate !== "undefined") {
return clearImmediate(t);
} else {
return clearTimeout(t);
}
}
return function (right, ms) {
return Aff.Async(function (cb) {
return function () {
var timer = setDelay(ms, cb(right()));
return function () {
return Aff.Sync(function () {
return right(clearDelay(ms, timer));
});
};
};
});
};
}();
exports._sequential = Aff.Seq;