异步编程总结-5
该文章阅读需要5分钟,更多文章请点击本人博客halu886
Promise的进阶知识
在API的暴露上,Promise比原生的事件侦听和触发更加优雅,但是原始的方式更加灵活,但是在一些经典的场景下,封装API并不高,还是值得一做。
Promise的秘诀在于队列的操作,当存在需要和服务器进行多次指令操作时,可以用deferred模式解决问题。
当有一组纯异步的API,操作如下
obj.api1(function(value1){
obj.api2(function(value2){
obj.api3(function(value3){
obj.api4(function(value4){
callback(value4)
}
}
}
})
当每个请求都依赖于上一个请求时,当业务一复杂,嵌套则非常不友好,也被称为“Pyramid of Doom”。也被译为回调金字塔。
如果尝试用普通函数进行展开
var handle1 = function (value1){
obj.api2(value1,handler2);
}
var handle2 = function (value2){
obj.api3(value2,handler3);
}
var handle3 = function (value3){
obj.api4(value3,handler4);
}
var handle4 = function (value4){
callback(value4);
}
obj.api1(handler1);
如果使用事件进行展开
var emitter = new event.Emitter();
emitter.on("step1",function(){
obj.api1(function(value1){
emitter.emit("step2",value1);
})
});
emitter.on("step2",function(value1){
obj.api2(value1,function(value2){
emitter.emit("step3",value2);
})
});
emitter.on("step3",function(value2){
obj.api3(value2,function(value3){
emitter.emit("step4",value3);
})
});
emitter.on("step4",function(value3){
obj.api4(value3,function(value4){
callback(value4);
})
});
emitter.emit("step1");
通过事件模式进行展开越来越糟糕了,代码行数变多了。
支持序列执行的Promise
理想的编程方式应该时前一个调用完后发起下一个调用,也就是所谓的链式调用。
promise()
.then(obj.api1)
.then(obj.api2)
.then(obj.api3)
.then(obj.api4)
.then(function(value4){
// Do something with value4
},function(error){
// Handle any error from step1 through step3
})
.done();
尝试改造一下,调用如下
var Deferred = function(){
this.promise = new Promise();
}
Deferred.prototype.resolve = function(obj){
var promise= this.promise;
var handler;
while((handler = promise.queue.shift())){
if(handler && handler.fulfilled){
var ret = handler.fulfilled(obj);
if(ret && ret.isPromise){
ret.queue = promise.queue;
this.promise = ret;
return;
}
}
}
}
// 失败态
Deferred.prototype.reject = function(err){
var promise = this.promise;
var handler;
while((handler = promise.queue.shift())){
if(handler && handler.error){
var ret = handler.error(err);
if(ret && ret.isPromise){
ret.queue = promise.queue;
this.promise = ret;
return;
}
}
}
};
// 生成回调函数
Deferred.prototype.callback = function(){
var that = this;
return function(err,file){
if(err){
return that.reject(err);
}
that.resolve(file);
};
};
var Promise = function(){
// 队列用于存储待执行的回调函数
this.queue = [];
this.isPromise = true;
}
Promise.prototype.then = function(fulfilledHandler,errorHandler,progressHandler){
var handler = {};
if(typeof fulfilledHandler === 'function'){
handler.fulfilled = fulfilledHandler;
}
if(typeof errorHander === 'function'){
handler.error = errorHandler;
}
this.queue.push(handler);
return this;
}
这里我们以读取两次文件为例,第二个文件的读取依赖于第一个文件的内容。
var readFile1 = function(file,encoding){
var deferred = new Deferred();
fs.readFile(file,encoding,deferred.callback());
return deferred.promise;
}
var readFile2 = function (file,encoding){
var deferred = new Deferred();
fs.readFile(file,encoding,deferred.callback());
return deferred.promise;
}
readFile1('file1.txt','utf8').then(function(file1){
return readFile2(file1.trim().'utf8');
}).then(function(file2){
console.log(file2);
})
让Promise支持链式,主要有两个步骤:
- 让所有回调都存在队列中
- Promise完成,逐个执行回调,当返回Promise时,当前Deferred对象的 promise引用指向新的 Promise对象。且余下的回调转交给它。
将API Promise化
这里提供一个方法可以实现 API批量 Promise化
// smooth(fs.readFile);
var smooth = function(method){
return function(){
var deferred = new Deferred();
var args = Array.prototype.slice.call(arguments,1);
args.push(deferred.callback());
method.apply(null,args);
return deferred.promise;
};
};
于是前两次文件的构造如下
var readFile1 = function(file,encoding){
var deferred = new Deferred();
fs.readFile(file,encoding,deferred.callback());
return deferred.promise;
}
var readFile2 = function(file,encoding){
var deferred = new Deferred();
fs.readFile(file,encoding,deferred.callback());
return deferred.promise;
}
可以简化为
var readFile = smooth(fs.readFile);
实现同样的效果时,代码量将会锐减到到
var readFile = smooth(fs.readFile);
readFile('file1.txt','utf8').then(function(file1){
return readFile(file1.trim(),'utf8');
}).then(function(file2){
console.log(file2);
})
以上知识点均来自<<深入浅出Node.js>>,更多细节建议阅读书籍:-)