Promise应用题

题目

今天遇到一道很有趣的题目,考察对于Promise的使用。题目是这样的,现在有一个函数requestData,接受id数组,并返回一个Promise对象(注:假设该函数会自动去除数组中的重复id)。这个函数有可能会被若干个模块在短时间内调用,如果几次调用的时间间隔不超过100毫秒的情况下,就将这几个调用合并成一个请求发送给服务器。现在要求实现这个函数getData(id),它返回一个Promise对象,可以实现以下调用结果


getData(1).then(function (data){console.log(data)});

setTimeout(function (){
    getData(2).then(function (data){console.log(data)});
}, 70);
setTimeout(function (){
    getData(3).then(function (data){console.log(data)});
}, 90);

setTimeout(function (){
    getData(4).then(function (data){console.log(data)});
}, 240);
// 上述代码执行后只调用requestData两次, 前三次调用合并在一个request中

分析

要解决这个需求,需要考虑2个问题:

  • 100毫秒内的请求如何归并在一起,集中发送;
  • 统一调用requestData后,如何让之前几次的Promise执行完成操作

第一个问题不难想到解决方案,通过window.setTimeout就可以完成。将最后一次的timerId记录下来,每次发送请求之前,都先clearTimeout,这样可以保证连续的调用只会最后执行一次批量发送,超时事件就是100ms。

第二个问题稍有难度。考虑连续调用3次的情况,由于前两次的调用,timer都被clear了,他们的promise需要在第三次调用发送请求返回结果的时候进行resolve,因此,需要将没有被resolve的promise存储下来。等到第三次请求返回之后,查找存储的promise中是否有对应的id数据,如果存在,就resolve它。

除了这两个问题,其实还有数据缓存方面的优化,因为连续的调用也有可能请求了相同id的数据,其实这时候就可以考虑通过缓存减少要请求的数据。当加上了数据缓存之后,就可能发生以下几种情况:

  • 要请求某个id的数据,已经在缓存中存在了,可以直接使用缓存的数据
  • 要请求的某个id的数据,缓存中虽然没有,但是已经有其他调用发送了请求,但结果还没返回,处在pending状态
  • 要请求的某个id的数据,缓存中没有,同时也没有任何其他调用发送了请求

针对第一种情况,只要直接返回一个reslove掉的Promise对象即可;

针对第二种情况,可以复用现有的Promise对象;

最复杂的就是第三种情况,因为在准备调用requestData之前,需要考虑是否存在连续的调用在100ms内可以合并。Timer的逻辑前面已经讲了。将这些考虑点结合以下,代码思路就非常清晰了。

代码



// 该函数返回一个Promise,Promise成功时,会返回idlist对应的结果集数组,每一个数组元素都包含id字段
var requestData = function (idList) {  
   // .....
};
var cache = {}; // 数据缓存
var last = {
    timer: -1,  // 记录最后一次的timerId
    data:[],  // 待请求的id
    promises: {}
};
var getData = function (id) {
    if(cache.hasOwnProperty(id)){
// 数据请求已经发送,但还是pending,复用现有的promise
        if(cache[id] instanceof Promise){ 
            return cache[id];
        }else{
//缓存中包含目标id的结果,直接返回
            return new Promise(function(resolve){
                resolve(cache[id]);
            });
        }
        
    }else{
//缓存中没有,同时没有包含目标id的pending的请求发出去了
        var promise = new Promise(function(resolve, reject){
            window.setTimeout(function(){
                promise.resolve = resolve;
                promise.reject = reject;
            });
// 清空前一次的timer
            window.clearTimeout(last.timer);
// 保存待查询的id,由于requestData自带去重处理,这里就不考虑id重复了
            last.data.push(id);
            last.timer = window.setTimeout(function(){
                cache[id] = new Promise(function(rs, rj){
                    requestData(last.data).then(function(rlist){
                        
                        for(var i=0; i < rlist.length; i++){
// 将结果保存到缓存
                            cache[rlist[i].id] = rlist[i];
                            // 如果存在promise,就resolve,并删除该promise
                            if(last.promises[rlist[i].uid]){
                                last.promises[rlist[i].uid].resolve(rlist[i]);
                                delete last.promises[rlist[i].uid];
                            }
                            
                        }
                    })
                    .catch(function (reason){
// 请求发生错误时,这里简单点,直接reject所有的promise
                        for (var id in last.promises) {
                            last.promises[id].reject();
                        }
                        last.promises = {};
                        last.data = [];
                    });
                    last.data = [];
                });
            }, 100);
        });
        last.promises[id] = promise;
        return last.promises[id];
    }
}
Show Comments

Get the latest posts delivered right to your inbox.