需要用nodejs实现一个临时文件的缓存模块。因为缓存的文件名是用一个自动增长的数字表示的,如果缓存文件的到期时间较短,那么到期之后删掉这个文件就可以继续使用这个名字了。但也可能某个缓存的时间很长,持久的占用这个名字。所以必须要在写进缓存文件之前判断这个缓存文件名是不是存在。
- 写入缓存实际上有三个步骤:
- 判断是否存在
- 创建文件
- 返回文件句柄
把这三个步骤封装一下可以得到以下方法:
var fs=require('fs');
// 方便起见我把stat,open都转成Promise的形式,否则写起来太乱了
['stat','open'].map(function(h){
fs[h+'Then']=function(){
var a=Array.prototype.slice.call(arguments);
return new Promise(function(resolve,reject){
a.push(function(e){
e?reject(e):resolve(Array.prototype.slice.call(arguments,1));
});
fs[h].apply(fs,a);
});
};
});
function getTempHandleThen(i){
return new Promise(function(resolve,reject){
fs.statThen(i).then(
function(){
reject({err:'already exists'});
},function(e){
if(e.errno!==-4058)return reject(e);
return fs.openThen(i,'w');
}
).then(
function(args){
resolve(args[0]);
},reject
);
});
}
理想的调用情况是这样的:
getTempHandleThen('test0').then(function(fd){
console.log('success',fd);
},function(e){
console.log('fail',e);
});
如果test0不存在,则会得到success的结果,否则是fail。 但是在并发的情况下,会得到一个错误的结果:
// 模拟10并发
for(var i=0;i<10;i++)
getTempHandleThen('test'+i%5).then(function(fd){
console.log('success',fd);
},function(e){
console.log('fail',e);
});
运行结果:
<center> </center>
如果一开始test0不存在的话,会得到10个success的结果。产生这种错误结果的原因是:这三个步骤异步进行,所以多个并行的回调会在第一步同时认为某一个文件不存在,然后对同一个文件进行写入。
同步阻塞可以解决问题,但是在单线程的js程序中这么做简直作死。所以解决办法必然是异步的,不难想到加锁然后以顺序队列执行这个办法,实现并不困难。真正的问题在于这个办法的api应该如何设计,必须调用简单,而且通用性强。根据以往的经验,我认为在外部包装一层函数是一个合适的办法。所以最后实现了这样代码:
function addLock(fn){
// lock是一个hash表,键名为锁的关键字,值为Promise队列。
// lock保证了同一个关键字的任务会添加到队列尾部,而不是立即执行。
var lock={};
// count也是一个hash表,键名同上,值为Promise队列中未执行任务的长度。
// count是为了当长度为0时对这个队列的引用全部删除,便于垃圾回收。
var count={};
// 返回的是一个新的函数,第一个参数为锁的关键字,第二个参数为调用原始函数时用到的参数数组
return function(lockid,args){
var dojo=function(){ return fn.apply(this,args); };
var clean=function(){
if(--count[lockid])return;
delete(lock[lockid]);
delete(count[lockid]);
};
lock[lockid]=lock[lockid]||Promise.resolve();
count[lockid]=count[lockid]||0;
count[lockid]++;
// 执行原始任务
lock[lockid]=lock[lockid].then(dojo,dojo);
// 如果这个锁下所有的任务都完成了,那么从lock中删掉引用
lock[lockid].then(clean,clean);
// 返回值和原始函数的返回值保持一致
return lock[lockid];
};
}
于是把上面的getTempHandleThen用addLock包装,在执行可以得到正确的结果了:
// 包装成可加锁的函数
_getTempHandleThen=addLock(getTempHandleThen);
// 模拟10并发
for(var i=0;i<10;i++)
// 用i%5作为锁的关键字
_getTempHandleThen(i%5,['test'+i%5]).then(function(fd){
console.log('success',fd);
},function(e){
console.log('fail',e);
});
相关文档
暂无
随便看看
畅言模块加载中