做有态度的前端团队

网易FEG前端团队

一个通用广告脚本数据加载的容错

最基本的需求

写一个通用的广告脚本文件,传递不同的广告ID,加载对应的数据出来,上报数据,最后显示出来

最简单的做法

每个站点,直接请求广告地址,带上不同参数,例如这样:

    var script = document.createElement("script");
    script.src = 'ad.com?id=id1&callback=cb';
    document.body.appendChild(script);

    function cb(param){
        //do something
        new Image().src = "report.com?id=id1";
    }

以上做法存在的问题:

  1. 每个站点都需要记住请求的地址,万一地址改了,全都要改
  2. 每个站点,都需要自己去写请求,以及回调
  3. 每个站点,都需要自己去上报数据
  4. 请求失败了,怎么办?
  5. 请求时间很长,怎么办?

站点的使用,更应该关心的是:

  1. 统一的接口
  2. 只需要传ID和回调函数
  3. 返回需要的数据进行渲染页面
  4. 错误、失败、上报、超时等不需要关心

我们可以这样做

1、统一处理请求

采用类 jQuery的方式来进行jsonp请求,增加超时和错误监控

    //简易版jsonp
    ;var __GetScript = function(){
    
        //发送请求(闭包)
        //param object json参数
        //param.url 请求的地址
        //param.data 请求的参数
        //param.success 请求成功的回调
        //param.error 请求失败的回调
        function send(param){
        
            //随机函数名字,类似jquery
            var random = "script" + Math.floor(Math.random() * 100000 + 100000);
            //计时器Id,用于超时abort
            var timeId = 0;
            //创建script脚本
            var script = document.createElement("script");
            script.type = "text/javascript";
            //绑定脚本错误事件
            var onerror = script.onerror = function(){
                //如果计时器是空,则表示请求成功了
                if(!timeId)return false;
                clearTimeout(timeId);
                //回调用户错误函数
                param.error();
                //移除script标签以及对应成功函数
                document.body.removeChild(script);
                window.random = null;
            };
            //绑定全局回调成功函数
            window[random] = function(){
                
                if(!timeId)return false;
    
                clearTimeout(timeId);
                //将jsonp的参数列表,返回给用户的回调函数
                try{
                    param.success.apply(null,arguments);
                }
                catch(e){
                    //用户回调执行出错,执行错误函数
                    param.error();
                }
                //移除script标签以及对应成功函数
                document.body.removeChild(script);
                window.random = null;
            }
            
            //超时则直接返回失败,这里设置了3秒
            timeId = setTimeout(function(){
    
                //触发onerror事件
                onerror();
                
                timeId = 0;
                
            },3000);
            //script的请求,一定是在最后
            script.src = param.url + "?" + param.data + "&callback=" + random;
    
            document.body.appendChild(script);
        }
        //返回请求接口
        return send;
    }();
    
    //调用demo,这样看起来,就跟jquery的差不多了
    __GetScript({
        url : 'ad.com',
        data : 'id=id1',
        success : function(data){},
        error : function(){}
    });

2、超时、请求失败

服务器挂了,服务器卡住了,那广告位总不能显示空白吧?

  1. 用户第一次访问,服务器就挂了(这种表示无奈),显示一个加载失败的提示文案或者图标之类的,统一在广告位加上一个标识来处理

  2. 用户第一次访问成功过,但之后第二次访问的时候才挂的,那么可以考虑使用上一次成功的数据来展示

    2.1 每次请求成功,缓存成功数据,格式采用json格式,旧版使用json2来支持
    2.2 IE使用userData,标准浏览器用localStorage
    2.3 合并新请求的数据与旧的缓存数据

    //拉取数据成功
    function getDataSucc(data,pos,cb_list){
        //移除正在请求的提示
        $(".adbase-ctn").css("background","none");
        
        //获取本地缓存数据
        var locData = getDataByLoc(pos);

        //合并与覆盖最新数据到本地中
        locData = extendData(locData,data);

        //记录当前数据,JSON对应在旧浏览器需要用第三方库`json2`
        LocalData.set(pos,JSON.stringify(locData));

        //回调给用户
        for(var i=0;i<cb_list.length;i++){
            cb_list[i](true,locData);
        }
    }

    //获取本地缓存数据
    function getDataByLoc(pos){
        //LocalData对象是操作userData和localStorage
        var data = LocalData.get(pos);

        if(!data)return {};
        //从缓存中读取的字符串,转成json对象
        return JSON.parse(data);
    }

    //合并新旧数据
    function extendData(oldData,newData){

        for(var key in newData){

            //本地没有这个数据,合并
            if(!oldData[key] || oldData[key].length < 1){
                oldData[key] = newData[key];

                continue;
            }
            //本地跟新数据都有,合并
            if((oldData[key] && oldData[key].length) && (newData[key] && newData[key].length)){

                oldData[key] = newData[key];

                continue;
            }
            //新数据没有,本地数据有,不合并
            if((oldData[key] && oldData[key].length) && (!newData[key] || newData[key].length < 1)){

                continue;
            }
            //本地没有,新数据也没有
            if((!newData[key] || newData[key].length < 1) && (!oldData[key] || oldData[key].length < 1)){

                oldData[key] = [];
            }
        }

        return oldData;
    }

3、其它收尾细节

处理完请求和缓存这两块主要的,还有一些细节需要考虑

  • 在请求成功后,统一上报数据,该请求可以稍微往后,用setTimeout
  • 在有数据的情况下(可能只是部分),需要针对没有数据的广告位进行显示失败的提示,例如:
    //监测是否存在空数据的广告位
    function checkNoDataCtn(){
        //循环遍历容器是否有子元素
        $(".adbase-ctn").each(function(index,item){

            if($(item).contents().length < 1)$(item).css("background","url(http://res.nie.netease.com/comm/js/nie/util/img/error.png) center center no-repeat");
        });
    }
  • IE7的userDatakey只支持的规则和定义变量一致,不能带有类似,;这种分隔符
  • 低版本的IE浏览器,即使script请求失败了,也不会调用onerror事件,统一采用超时机制就好了

PS:更多代码详情,请参考:adbase

手机阅读请扫描下方二维码:

已有 4 条评论

  1. __GetScript 这个函数,里面的success方法没起作用。

  2. 不错哦,赞一个,求认识,求回访 http://www.xevip.cn

  3. 文章很好~!赞 http://www.viplinger.cn

  4. 文章很好~!赞 欢迎回访:www.3gwb.com

添加新评论

ali-40.gifali-41.gifali-42.gifali-43.gifali-44.gifali-45.gifali-46.gifali-47.gifali-48.gifali-49.gifali-50.gifali-51.gifali-52.gifali-53.gifali-54.gifali-55.gifali-56.gifali-57.gifali-58.gifali-59.gifali-60.gifali-61.gif