-
Notifications
You must be signed in to change notification settings - Fork 535
/
flow.js
298 lines (294 loc) · 10.6 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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
//=========================================
// 流程模块v1 by 司徒正美 (流程控制,消息交互)
//=========================================
define("flow", ["class"], function($) {
//观察者模式
$.Observer = $.factory({
init: function(target) {
this._events = {};
if (target === "string") {
this._target = this;
var self = this;
target.replace(/(\w+)(?:\(([^\)]+)\))*/ig, function(a, b, c) {
if (isFinite(c)) {
self[a] = +c;
} else {
self[a] = c || true;
}
});
} else {
this._target = target;
}
},
bind: function(type, callback) {
var listeners = this._events[type]
if (listeners) {
listeners.push(callback)
} else {
this._events[type] = [callback]
}
return this;
},
once: function(type, callback) {
var self = this;
var wrapper = function() {
callback.apply(self, arguments);
self.unbind(type, wrapper);
};
this.bind(type, wrapper);
return this;
},
unbind: function(type, callback) {
var n = arguments.length;
if (n === 0) {
this._events = {};
} else if (n === 1) {
this._events[type] = [];
} else {
var listeners = this._events[type] || [];
var i = listeners.length;
while (--i > -1) {
if (listeners[i] === callback) {
return listeners.splice(i, 1);
}
}
}
return this;
},
fire: function(type) {
var listeners = (this._events[type] || []).concat(); //防止影响原数组
if (listeners.length) {
var target = this._target,
args = $.slice(arguments);
if (this.rawFire) {
args.shift()
} else {
args[0] = {
type: type,
target: target
};
}
for (var i = 0, callback; callback = listeners[i++]; ) {
callback.apply(target, args);
}
}
}
});
//用于处理需要通过N个子步骤才能完成的一些操作
//多路监听,收集每个子步骤的执行结果,触发最终回调,解耦回调的深层嵌套
$.Flow = $.Observer.extend({
init: function(timeout) {
this._fired = {}; //用于收集fire或order的参数(去掉第一个事件参数)
},
fire: function(type, args) {
var calls = this._events,
normal = 2,
listeners, ev;
while (normal--) {
ev = normal ? type : last;
listeners = calls[ev];
if (listeners && listeners.length) {
args = $.slice(arguments, 1);
if (normal) { //在正常的情况下,我们需要传入一个事件对象,当然与原生事件对象差很远,只有两个属性
if (this._events[ev]) {
this._fired[ev] = args.concat();
}
args.unshift({
type: type,
target: this._target
});
}
for (var i = 0, callback; callback = listeners[i++]; ) {
//第一次执行目标事件,第二次执行最后的回调
callback.apply(this, args);
}
} else {
break;
}
}
return this;
},
/**待所有子步骤都执行过一遍后,执行最后的回调,之后每次都执行最后的回调以局部刷新数据
require("flow",function($){
var flow = new $.Flow;
flow.refresh("aaa,bbb,ccc",function(){
$.log(Array.apply([],arguments))
});
flow.fire("aaa",1)//没反应
flow.fire("bbb",2)//没反应
flow.fire("ccc",3)//[1,2,3]
flow.fire("aaa",4)//[4,2,3]
flow.fire("bbb",5)//[4,5,3]
flow.fire("ccc",6)//[4,5,6]
})
*/
refresh: function() {
Array.prototype.push.call(arguments, false);
_assign.apply(this, arguments);
return this;
},
/**待所有子步骤都执行过一遍后,执行最后的回调,然后清后所有数据,重新开始这过程
require("flow",function($){
var flow = new $.Flow;
flow.reload("aaa,bbb,ccc",function(){
$.log(Array.apply([],arguments))
});
flow.fire("aaa",1)//没反应
flow.fire("bbb",2)//没反应
flow.fire("ccc",3)//[1,2,3]
flow.fire("aaa",4)//没反应
flow.fire("bbb",5)//没反应
flow.fire("ccc",6)//[4,5,6]
})
*/
reload: function() {
Array.prototype.push.call(arguments, true);
_assign.apply(this, arguments);
return this;
},
/**配合refresh使用,相当于一种特殊的fire,要求用户在fire时,按refresh的第一个参数绑定的顺序依次执行
require("flow",function($){
var flow = new $.Flow;
flow.refresh("aaa,bbb,ccc", function(){
$.log(Array.apply([],arguments))
});
flow.order("aaa",1);//没有反应
flow.order("aaa",2);//出错,它本应该触发bbb,但没有警告,它只要求你重来
flow.order("aaa",3);//没有反应
flow.order("bbb",4);//没有反应
flow.order("ccc",5);//[4,5,6]
})
//此外我们也可以在构造器传入时间限制,如果new $.Flow(4000),从第一个fire这个时间点到4秒内,
//没有能依次触发完aaa,bbb,ccc,则清空已有数据,重新开始
// 可以应用于游戏中的QTE http://baike.baidu.com/view/1398321.htm,我们只需要绑定keydown事件,在回调中调用flow.order(e.which);
*/
order: function(type) {
if (this._events[type]) {
var cur = this._queue.shift();
if (!this.timestamp) {
this.timestamp = new Date - 0;
}
var limit = true;
if (this.timeout && (new Date - this.timestamp > this.timeout)) {
limit = false;
}
if (type === cur && limit) {
this.fire.apply(this, arguments);
} else {
this._queue = this._order.concat();
this._fired = {};
delete this.timestamp;
}
}
},
/**一个子步骤在重复执行N遍后,执行最后的回调
require("flow", function($) {
var flow = new $.Flow;
flow.repeat("aaa", 4, function() {
$.log(Array.apply([], arguments))
});
flow.fire("aaa", 1) //没反应
flow.fire("aaa", 2) //没反应
flow.fire("aaa", 3) //没反应
flow.fire("aaa", 4) //[1,2,3,4]
flow.fire("aaa", 5) //没反应
flow.fire("aaa", 6) //没反应
flow.fire("aaa", 7) //没反应
flow.fire("aaa", 8) //[5,6,7,8]
})
*/
repeat: function(type, times, callback) {
var old = times,
that = this,
ret = [];
function wrapper() {
ret.push.apply(ret, $.slice(arguments, 1));
if (--times === 0) {
callback.apply(this._target, ret);
times = old;
ret = [];
}
}
that.bind(type, wrapper);
return this;
},
//用于提供一个简单的成功回调
done: function(callback) {
var that = this;
return function(err, data) {
if (err) {
return that.fire('error', err);
}
if (typeof callback === 'string') {
return that.fire(callback, data);
}
if (arguments.length <= 2) {
return callback(data);
}
var args = $.slice(arguments, 1);
callback.apply(null, args);
};
},
//用于提供一个简单的错误回调
fail: function(callback) {
var that = this;
that.once('error', function(err) {
that.unbind();
callback(err);
});
return this;
}
});
$.Flow.create = function(names, callback, errorback) {
var that = new $.Flow;
var args = names.match($.rword) || [];
if (typeof errorback === "function") {
that.fail(errorback);
}
args.push(callback);
that.refresh.apply(that, args);
return that;
};
var last = "$" + Date.now();
var _assign = function(name, callback, reload) {
var flow = this,
times = 0,
uniq = {},
events = name.match($.rword),
length = events.length;
if (!events.length) {
return this;
}
this._queue = events.concat();
this._order = events;
function bind(key) {
flow.bind(key, function() {
if (!uniq[key]) {
uniq[key] = true;
times++;
}
});
}
//绑定所有子事件
for (var index = 0; index < length; index++) {
bind(events[index]);
}
function lastFn() {
//如果没有达到目标次数, 或事件类型之前没有指定过
if (times < length) {
return;
}
var result = [];
for (var index = 0; index < length; index++) {
result.push.apply(result, flow._fired[events[index]]);
}
if (reload) {
uniq = {};
times = 0;
}
callback.apply(flow, result);
}
flow.bind(last, lastFn);
};
return $;
})