UNPKG

easy-magix

Version:

view manager framewrok

704 lines (676 loc) 22.1 kB
/* 一个请求send后,应该取消吗? 参见xmlhttprequest的实现 https://chromium.googlesource.com/chromium/blink/+/master/Source/core https://chromium.googlesource.com/chromium/blink/+/master/Source/core/xmlhttprequest/XMLHttpService.cpp 当请求发出,服务器接受到之前取消才有用,否则连接已经建立,数据开始传递,中止只会浪费。 但我们很难在合适的时间点abort,而且像jsonp的,我们根本无法abort掉,只能任数据返回 然后我们在自已的代码中再去判断、决定回调是否调用 那我们是否可以这样做: 1. 不取消请求 2. 请求返回后尽可能的处理保留数据,比如缓存。处理完成后才去决定是否调用回调(Service_Send中的Done实现) 除此之外,我们还要考虑 1. 跨请求对象对同一个缓存的接口进行请求,而某一个销毁了。 Service.add([{ name:'Test', url:'/test', cache:20000 }]); var r1=new Service(); r1.all('Test',function(e,m){ }); var r2=new Service(); r2.all('Test',function(e,m){ }); r1.destroy(); 如上代码,我们在实现时: r2在请求Test时,此时Test是可缓存的,并且Test已经处于r1请求中了,我们不应该再次发起新的请求,只需要把回调排队到r1的Test请求中即可。参见代码:Service_Send中的for,Service.cached。 当r1进行销毁时,并不能贸然销毁r1上的所有请求,如Test请求不能销毁,只能从回调中标识r1的回调不能再被调用。r1的Test还要继续,参考上面讨论的请求应该取消吗。就算能取消,也需要查看Test的请求中,除了r1外是否还有别的请求要用,我们示例中是r2,所以仍然继续请求。参考Service#.destroy */ /** * Bag类 * @name Bag * @beta * @module service * @constructor * @property {String} id bag唯一标识 */ var Bag = function() { this.id = G_Id('b'); this.$ = {}; }; G_Mix(Bag[G_PROTOTYPE], { /** * @lends Bag# */ /** * 获取属性 * @param {String} [key] 要获取数据的key * @param {Object} [dValue] 当根据key取到的值为falsy时,使用默认值替代,防止代码出错 * @return {Object} * @example * new Serice().one({ * name:'Test' * },function(error,bag){ * var obj=bag.get();//获取所有数据 * * var list=bag.get('list',[]);//获取list数据,如果不存在list则使用空数组 * * var count=bag.get('data.info.count',0);//获取data下面info下count的值,您无须关心data下是否有info属性 * console.log(list); * }); */ get: function(key, dValue, udfd) { var me = this; //var alen = arguments.length; /* 目前只处理了key中不包含.的情况,如果key中包含.则下面的简单的通过split('.')的方案就不行了,需要改为: var reg=/[^\[\]]+(?=\])|[^.\[\]]+/g; var a=['a.b.c','a[b.c].d','a[0][2].e','a[b.c.d][eg].a.b.c','[e.g.d]','a.b[c.d.fff]']; for(var i=0,one;i<a.length;i++){ one=a[i]; console.log(one.match(reg)) } 但考虑到key中有.的情况非常少,则优先使用性能较高的方案 或者key本身就是数组 */ var hasDValue = dValue != udfd; var $attrs = me.$; var attrs = $attrs; if (key) { var tks = G_IsArray(key) ? G_Slice.call(key) : (key + G_EMPTY).split('.'), tk; while ((tk = tks.shift()) && attrs) { attrs = attrs[tk]; } if (tk) { attrs = udfd; } } var type; if (hasDValue && (type = G_Type(dValue)) != G_Type(attrs)) { if (DEBUG) { Magix_Cfg.error(Error('type neq:' + key + ' is not a(n) ' + type)); } attrs = dValue; } if (DEBUG && me.$m && me.$m.k) { //缓存中的接口不让修改数据 attrs = Safeguard(attrs); } return attrs; }, /** * 设置属性 * @param {String|Object} key 属性对象或属性key * @param {Object} [val] 属性值 */ set: function(key, val) { var me = this, t; if (!G_IsObject(key)) { t = {}; t[key] = val; key = t; } G_Mix(me.$, key); } }); var Service_FetchFlags_ONE = 1; var Service_FetchFlags_ALL = 2; var Service_CacheDone = function(cacheKey, err, fns) { fns = this[cacheKey]; //取出当前的缓存信息 if (fns) { delete this[cacheKey]; //先删除掉信息 G_ToTry(fns, err, fns.e); //执行所有的回调 } }; var Service_Task = function(done, host, service, total, flag, bagCache) { var doneArr = []; var errorArgs = G_NULL; var currentDoneCount = 0; return function(idx, err) { var bag = this; var newBag; currentDoneCount++; //当前完成加1 var mm = bag.$m; var cacheKey = mm.k; doneArr[idx + 1] = bag; //完成的bag var dispach = { bag: bag, error: err }; if (err) { //出错 errorArgs = err; //errorArgs[idx] = err; //记录相应下标的错误信息 //G_Mix(errorArgs, err); host.fire('fail', dispach); newBag = 1; //标记当前是一个新完成的bag,尽管出错了 } else if (!bagCache.has(cacheKey)) { //如果缓存对象中不存在,则处理。注意在开始请求时,缓存与非缓存的都会调用当前函数,所以需要在该函数内部做判断处理 if (cacheKey) { //需要缓存 bagCache.set(cacheKey, bag); //缓存 } //bag.set(data); mm.t = G_Now(); //记录当前完成的时间 var after = mm.a; if (after) { //有after G_ToTry(after, bag, bag); } if (mm.x) { //需要清理 host.clear(mm.x); } host.fire('done', dispach); newBag = 1; } if (!service.$o) { //service.$o 当前请求被销毁 var finish = currentDoneCount == total; if (finish) { service.$b = 0; if (flag == Service_FetchFlags_ALL) { //all doneArr[0] = errorArgs; G_ToTry(done, doneArr, service); } } if (flag == Service_FetchFlags_ONE) { //如果是其中一个成功,则每次成功回调一次 G_ToTry(done, [err ? err : G_NULL, bag, finish, idx], service); } } if (newBag) { //不管当前request或回调是否销毁,均派发end事件,就像前面缓存一样,尽量让请求处理完成,该缓存的缓存,该派发事件派发事件。 host.fire('end', dispach); } }; }; /** * 获取attrs,该用缓存的用缓存,该发起请求的请求 * @private * @param {Object|Array} attrs 获取attrs时的描述信息,如:{name:'Home',urlParams:{a:'12'},formParams:{b:2}} * @param {Function} done 完成时的回调 * @param {Integer} flag 获取哪种类型的attrs * @param {Boolean} save 是否是保存的动作 * @return {Service} */ var Service_Send = function(me, attrs, done, flag, save) { if (me.$o) return me; //如果已销毁,返回 if (me.$b) { //繁忙,后续请求入队 return me.enqueue(function() { Service_Send(this, attrs, done, flag, save); }); } me.$b = 1; //标志繁忙 var host = me.constructor; //var bagCache = host.$c; //存放bag的Cache对象 var bagCacheKeys = host.$r; //可缓存的bag key if (!G_IsArray(attrs)) { attrs = [attrs]; } var total = attrs.length; var remoteComplete = Service_Task(done, host, me, total, flag, host.$c); /*#if(modules.serviceCombine){#*/ var combineBags = [], combineCbs = []; /*#}#*/ for (var i = 0, bag; i < total; i++) { bag = attrs[i]; if (bag) { var bagInfo = host.get(bag, save); //获取bag信息 var bagEntity = bagInfo.e; var cacheKey = bagEntity.$m.k; //从实体上获取缓存key var complete = G_Proxy(remoteComplete, bagEntity, i); //包装当前的完成回调 var cacheList; if (cacheKey && bagCacheKeys[cacheKey]) { //如果需要缓存,并且请求已发出 bagCacheKeys[cacheKey].push(complete); //放到队列中 } else if (bagInfo.u) { //需要更新 if (cacheKey) { //需要缓存 cacheList = [complete]; cacheList.e = bagEntity; bagCacheKeys[cacheKey] = cacheList; complete = G_Proxy(Service_CacheDone, bagCacheKeys, cacheKey); //替换回调,详见Service_CacheDone } /*#if(modules.serviceCombine){#*/ combineBags.push(bagEntity); combineCbs.push(complete); /*#}else{#*/ host.$s(bagEntity, complete); /*#}#*/ } else { //不需要更新时,直接回调 complete(); } } } /*#if(modules.serviceCombine){#*/ if (combineBags.length) { var tempBag = new Bag(); tempBag.set('bags', combineBags); tempBag._cbs = combineCbs; host.$s(tempBag, function() { var list = tempBag._cbs; for (var i = 0; i < list.length; i++) { list[i](); } }); } /*#}#*/ return me; }; /** * 接口请求服务类 * @name Service * @constructor * @beta * @module service * @borrows Event.on as on * @borrows Event.fire as fire * @borrows Event.off as off * @example * var S = Magix.Service.extend(function(bag,callback){ * $.ajax({ * url:bag.get('url'), * success:function(data){ * bag.set('data',data)//设置数据 * callback();//通知内部完成数据请求 * }, * error:function(msg){ * callback(msg);//出错 * } * }) * }); * // 添加接口 * S.add({ * name:'test', * url:'/test', * cache:1000*60 //缓存一分钟 * }); * // 使用接口 * var s=new S(); * s.all('test',function(err,bag){ * console.log(err,bag); * }); */ var Service = function() { var me = this; me.id = G_Id('s'); if (DEBUG) { me.id = G_Id('\x1es'); setTimeout(function() { if (!me.$c) { console.warn('beware! You should use view.capture to connect Service and View'); } }, 1000); } me.$q = []; }; G_Mix(Service[G_PROTOTYPE], { /** * @lends Service# */ /** * 获取attrs,所有请求完成回调done * @function * @param {Object|Array} attrs 获取attrs时的描述信息,如:{name:'Home',cacheKey:'key',urlParams:{a:'12'},formParams:{b:2}} * @param {Function} done 完成时的回调 * @return {Service} * @example * new Service().all([{ * name:'Test1' * },{ * name:'Test2' * }],function(err,bag1,bag2){ * console.log(arguments); * }); */ all: function(attrs, done) { return Service_Send(this, attrs, done, Service_FetchFlags_ALL); }, /** * 保存attrs,所有请求完成回调done * @function * @param {Object|Array} attrs 保存attrs时的描述信息,如:{name:'Home',urlParams:{a:'12'},formParams:{b:2}} * @param {Function} done 完成时的回调 * @return {Service} * @example * // 同all,但与all不同的是,当指定接口缓存时,all方法会优先使用缓存,而save方法则每次都会发送请求到服务器,忽略掉缓存。同时save更语义化 */ save: function(attrs, done) { return Service_Send(this, attrs, done, Service_FetchFlags_ALL, 1); }, /** * 获取attrs,其中任意一个成功均立即回调,回调会被调用多次。注:当使用promise时,不存在该方法。 * @function * @param {Object|Array} attrs 获取attrs时的描述信息,如:{name:'Home',cacheKey:'key',urlParams:{a:'12'},formParams:{b:2}} * @param {Function} callback 完成时的回调 * @beta * @return {Service} * @example * //代码片断: * var s = new Service().one([ * {name:'M1'}, * {name:'M2'}, * {name:'M3'} * ],function(err,bag){//m1,m2,m3,谁快先调用谁,且被调用三次 * if(err){ * alert(err.msg); * }else{ * alert(bag.get('name')); * } * }); */ one: function(attrs, done) { return Service_Send(this, attrs, done, Service_FetchFlags_ONE); }, /** * 前一个all,one或save任务做完后的下一个任务 * @param {Function} callback 当前面的任务完成后调用该回调 * @return {Service} * @beta * @example * var r = new Service().all([ * {name:'M1'}, * {name:'M2'} * ],function(err,bag1,bag2){ * r.dequeue(['args1','args2']); * }); * r.enqueue(function(args1,args2){ * alert([args1,args2]); * }); */ enqueue: function(callback) { var me = this; if (!me.$o) { me.$q.push(callback); me.dequeue(me.$a); } return me; }, /** * 做下一个任务 * @param {Array} preArgs 传递的参数 * @beta * @example * var r = new Service(); * r.all('Name',function(e,bag){ * r.dequeue([e,bag]); * }); * r.enqueue(function(e,result){//result为m * r.all('NextName',function(e,bag){ * r.dequeue([e,bag]); * }); * }); * * r.enqueue(function(e,bag){//m===queue m; * console.log(e,bag); * r.dequeue([e,bag]); * }); * * r.enqueue(function(e,bag){ * console.log(e,bag); * }); * * //当出错时,e为出错的信息 */ dequeue: function() { var me = this, a = G_Slice.call(arguments); if (!me.$b && !me.$o) { me.$b = 1; setTimeout(function() { //前面的任务可能从缓存中来,执行很快 me.$b = 0; if (!me.$o) { //不清除setTimeout,但在回调中识别是否调用了destroy方法 var one = me.$q.shift(); if (one) { G_ToTry(one, me.$a = a, me); } } }, 0); } }, /** * 销毁当前请求,不可以继续发起新请求,而且不再调用相应的回调 */ destroy: function(me) { me = this; me.$o = 1; //只需要标记及清理即可,其它的不需要 me.$q = 0; } /** * 当Service发送请求前触发 * @name Service.begin * @event * @param {Object} e 事件对象 * @param {Bag} e.bag bag对象 * @example * var S = Magix.Service.extend({ * //codes * }); * * S.on('begin',function(e){//监听所有的开始请求事件 * console.log(e); * }); */ /** * 当Service结束请求时触发(成功或失败均触发) * @name Service.end * @event * @param {Object} e 事件对象 * @param {Bag} e.bag bag对象 * @param {String} e.error 当请求出错时,error是出错的消息 */ /** * 当Service发送请求失败时触发 * @name Service.fail * @event * @param {Object} e 事件对象 * @param {Bag} e.bag bag对象 * @param {String} e.error 当请求出错时,error是出错的消息 */ /** * 当Service发送请求成功时触发 * @name Service.done * @event * @param {Object} e 事件对象 * @param {Bag} e.bag bag对象 */ }); var Manager_DefaultCacheKey = function(meta, attrs, arr) { arr = [JSONStringify(attrs), JSONStringify(meta)]; return arr.join(G_SPLITER); }; var Manager_ClearCache = function(v, ns, cache, mm) { mm = v && v.$m; if (mm && ns[mm.n]) { cache.del(mm.k); } }; var Service_Manager = G_Mix({ /** * @lends Service */ /** * 添加元信息 * @param {Object} attrs 信息属性 */ add: function(attrs) { var me = this; var metas = me.$m; if (!G_IsArray(attrs)) { attrs = [attrs]; } for (var i = attrs.length, bag, name; i--;) { bag = attrs[i]; if (bag) { name = bag.name; bag.cache = bag.cache | 0; metas[name] = bag; } } }, /** * 创建bag对象 * @param {Object} attrs bag描述信息对象 * @return {Bag} */ create: function(attrs) { var me = this; var meta = me.meta(attrs); var cache = (attrs.cache | 0) || meta.cache; var entity = new Bag(); entity.set(meta); entity.$m = { n: meta.name, a: meta.after, x: meta.cleans, k: cache && Manager_DefaultCacheKey(meta, attrs) }; if (G_IsObject(attrs)) { entity.set(attrs); } var before = meta.before; if (before) { G_ToTry(before, entity, entity); } me.fire('begin', { bag: entity }); return entity; }, /** * 获取bag注册时的元信息 * @param {String|Object} attrs 名称 * @return {Object} * @example * var S = Magix.Service.extend({ * //extend code * }); * * S.add({ * name:'test', * url:'/test' * }); * * console.log(S.meta('test'),S.meta({name:'test'}));//这2种方式都可以拿到add时的对象信息 */ meta: function(attrs) { var me = this; var metas = me.$m; var name = attrs.name || attrs; var meta = metas[name]; return meta || attrs; }, /** * 获取bag对象,优先从缓存中获取 * @param {Object} attrs bag描述信息对象 * @param {Boolean} createNew 是否是创建新的Bag对象,如果否,则尝试从缓存中获取 * @return {Object} */ get: function(attrs, createNew) { var me = this; var entity, update; if (!createNew) { entity = me.cached(attrs); } if (!entity) { entity = me.create(attrs); update = 1; } return { e: entity, u: update }; }, /** * 根据name清除缓存的attrs * @param {String|Array} names 字符串或数组 * @example * var S = Magix.Service.extend({ * //extend code * }); * * S.add({ * name:'test', * url:'/test', * cache:1000*60 * }); * * var s = new Service(); * s.all('test'); * s.all('test');//from cache * S.clear('test'); * s.all('test');//fetch from server */ clear: function(names) { this.$c.each(Manager_ClearCache, G_ToMap((names + G_EMPTY).split(G_COMMA))); }, /** * 从缓存中获取bag对象 * @param {Object} attrs * @return {Bag} * @example * var S = Magix.Service.extend({ * //extend code * }); * * S.add({ * name:'test', * url:'/test', * cache:1000*60 * }); * * S.cached('test');//尝试从缓存中获取bag对象 */ cached: function(attrs) { var me = this; var bagCache = me.$c; var entity; var cacheKey; var meta = me.meta(attrs); var cache = (attrs.cache | 0) || meta.cache; if (cache) { cacheKey = Manager_DefaultCacheKey(meta, attrs); } if (cacheKey) { var requestCacheKeys = me.$r; var info = requestCacheKeys[cacheKey]; if (info) { //处于请求队列中的 entity = info.e; } else { //缓存 entity = bagCache.get(cacheKey); if (entity && G_Now() - entity.$m.t > cache) { bagCache.del(cacheKey); entity = 0; } } } return entity; } }, Event); /** * 继承 * @lends Service * @param {Function} sync 接口服务同步数据方法 * @param {Integer} [cacheMax] 最大缓存数,默认20 * @param {Integer} [cacheBuffer] 缓存缓冲区大小,默认5 * @return {Function} 返回新的接口类 * @example * var S = Magix.Service.extend(function(bag,callback){ * $.ajax({ * url:bag.get('url'), * success:function(data){ * bag.set('data',data); * callback(); * }, * error:function(msg){ * callback({message:msg}); * } * }) * },10,2);//最大缓存10个接口数据,缓冲区2个 */ Service.extend = function(sync, cacheMax, cacheBuffer) { var me = this; var NService = function() { me.call(this); }; NService.$s = sync; NService.$c = new G_Cache(cacheMax, cacheBuffer); NService.$r = {}; NService.$m = {}; return G_Extend(NService, me, G_NULL, Service_Manager); }; Magix.Service = Service;