-
Notifications
You must be signed in to change notification settings - Fork 15
/
flow.js
163 lines (138 loc) · 4.72 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Javascript Library for Multi-step Asynchronous Logic
// Version 0.2.3
// 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.
// @param {String} resultId An identifier to get the result of a multi call.
flowState.MULTI = function(resultId) {
flowState.__multiCount += 1;
return function() {
flowState.__multiCount -= 1;
flowState.__multiOutputs.push(arguments);
if (resultId) {
var result = arguments.length <= 1 ? arguments[0] : arguments
flowState.__multiOutputs[resultId] = result;
}
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() {
var flow = typeof exports != 'undefined' ? exports : window.flow;
applyArgs(flow.define, flow, 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();
}
);
// helper methods
function anyError(results) {
var r, _i, _len;
for (_i = 0, _len = results.length; _i < _len; _i++) {
r = results[_i];
if (r[0]) {
return r[0];
}
}
return null;
}
// export our functions
if (typeof exports !== "undefined") {
exports.define = define;
exports.exec = exec;
exports.serialForEach = serialForEach;
exports.anyError = anyError;
}
else if (typeof window !== "undefined") {
window.flow = {
define: define,
exec: exec,
serialForEach: serialForEach,
anyError: anyError
};
}
})();