-
Notifications
You must be signed in to change notification settings - Fork 2
/
flow.js
142 lines (119 loc) · 4.13 KB
/
flow.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Javascript Library for Multi-step Asynchronous Logic
// Version 0.2
// Copyright (c) 2010 William R. Conant, WillConant.com
// Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
(function(){
// converts native arguments object to an array and applies function
function applyArgs(func, thisObj, args) {
return func.apply(thisObj, Array.prototype.slice.call(args));
}
// defines a flow given any number of functions as arguments
function define() {
var thisFlow = function() {
applyArgs(thisFlow.exec, thisFlow, arguments);
}
thisFlow.blocks = arguments;
thisFlow.exec = function() {
// The flowState is the actual object each step in the flow is applied to. It acts as a
// callback to the next function. It also maintains the internal state of each execution
// and acts as a place for users to save values between steps of the flow.
var flowState = function() {
if (flowState.__frozen) return;
if (flowState.__timeoutId) {
clearTimeout(flowState.__timeoutId);
delete flowState.__timeoutId;
}
var blockIdx = flowState.__nextBlockIdx ++;
var block = thisFlow.blocks[blockIdx];
if (block === undefined) {
return;
}
else {
applyArgs(block, flowState, arguments);
}
}
// __nextBlockIdx specifies which function is the next step in the flow.
flowState.__nextBlockIdx = 0;
// __multiCount is incremented every time MULTI is used to createa a multiplexed callback
flowState.__multiCount = 0;
// __multiOutputs accumulates the arguments of each call to callbacks generated by MULTI
flowState.__multiOutputs = [];
// REWIND signals that the next call to thisFlow should repeat this step. It allows you
// to create serial loops.
flowState.REWIND = function() {
flowState.__nextBlockIdx -= 1;
}
// MULTI can be used to generate callbacks that must ALL be called before the next step
// in the flow is executed. Arguments to those callbacks are accumulated, and an array of
// of those arguments objects is sent as the one argument to the next step in the flow.
flowState.MULTI = function() {
flowState.__multiCount += 1;
return function() {
flowState.__multiCount -= 1;
flowState.__multiOutputs.push(arguments);
if (flowState.__multiCount === 0) {
var multiOutputs = flowState.__multiOutputs;
flowState.__multiOutputs = [];
flowState(multiOutputs);
}
}
}
// TIMEOUT sets a timeout that freezes a flow and calls the provided callback. This
// timeout is cleared if the next flow step happens first.
flowState.TIMEOUT = function(milliseconds, timeoutCallback) {
if (flowState.__timeoutId !== undefined) {
throw new Error("timeout already set for this flow step");
}
flowState.__timeoutId = setTimeout(function() {
flowState.__frozen = true;
timeoutCallback();
}, milliseconds);
}
applyArgs(flowState, this, arguments);
}
return thisFlow;
}
// defines a flow and evaluates it immediately. The first flow function won't receive any arguments.
function exec() {
applyArgs(exports.define, exports, arguments)();
}
// a very useful flow for serial execution of asynchronous functions over a list of values
// (idea suggested by John Wright, http://github.com/mrjjwright)
var serialForEach = define(
function(items, job, between, finish) {
this.items = items;
this.curItem = 0;
this.job = job;
this.between = between;
this.finish = finish;
this();
},function() {
if (this.curItem > 0 && this.between) {
applyArgs(this.between, this, arguments);
}
if (this.curItem >= this.items.length) {
this();
}
else {
this.REWIND();
this.curItem += 1;
this.job(this.items[this.curItem - 1]);
}
},function() {
if (this.finish) this.finish();
}
);
// export our functions
if (exports !== undefined) {
exports.define = define;
exports.exec = exec;
exports.serialForEach = serialForEach;
}
else if (window !== undefined) {
window.flow = {
define: define,
exec: exec,
serialForEach: serialForEach
};
}
})();