在这篇文章中,我会介绍另外一种异步编程的解决方案:Promise/Deferred模式。这种模式最早出现于Dojo的代码中,09年被Kris Zyp抽象为一个提议草案,发布于CommonJS规范中,并抽象出Promise/A、Promise/B、Promise/D这样典型的异步Promise/Deferred模型,这使得异步操作可以以一种优雅的方式出现。他最大的特点就是可以先执行异步调用,然后延迟传递处理操作。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。本篇文章通过Promise/A模式来作为敲门砖来简略介绍Promise/Deferred模式,以ES6的Promise实现来重点介绍Promise的特性以及对于异步编程问题的解决方案。
1.Promise/A的定义。
Promise/A提议对单个异步操作有如下规定:
1.Promise操作只会有三个状态:未完成,完成,失败
2.他的状态转换只能是:未完成 => 完成 和 未完成 => 失败两种,并且转换是不可逆,并且完成和失败之间不能相互转换。
3.状态一旦变化就不能被更改。
如下图所示:
而在api的定义上,最低要具备一个then方法即可,并且then方法的行为包括:
1.接受完成态,错误态时的回调方法,以便在Promise状态改变时,调用相应回调方法。
2.只接受函数
3.then方法会继续返回一个Promise,以实现链式调用。
其实then方法的行为只是将他接受的回调函数储存起来,在某一刻Promise状态改变时,再进行调用罢了。
示例代码如下:
const myPromise = function () {
this.handle = {}; // 储存处理函数
};
// then的作用在于把,成功和失败的回调给储存起来。以方便在以后的某个时刻调用
myPromise.prototype.then = function(resolveHandler,rejectHandler) {
let handle = {};
if(typeof resolve == "function") {
handle.resolve = resolveHandler
}
if(typeof reject == "function") {
handle.reject = rejectHandler
}
this.handle = handle;
}
不过如果要完整走完流程,还需要一个可以改变Promise状态的对象,而这个对象就是Deferred,即延迟对象。他用来改变Promise的状态,示例代码如下:
const myDeferred = function () {
this.status = "pending"; // 未完成的等待状态。
this.promise = new myPromise(); // 让deferred和一个promise进行关联,以控制promise的状态。
};
// 更改为完成状态,并执行相应的处理函数。
myDeferred.prototype.resolve = function (obj) {
this.status = "resolve";
let handle = this.promise.handle;
if(handle && handle.resolve) {
handle.resolve(obj);
}
}
// 更改为失败状态,并执行相应的处理函数。
myDeferred.prototype.reject = function (obj) {
this.status = "reject";
let handle = this.promise.handle;
if(handle && handle.reject) {
handle.reject(obj);
}
}
在这里,Deferred的作用主要就是更改状态,然后从关联的Promise中取出then方法所存储的相应的处理函数。
Promise和Deferred的整体关系图如下所示:
可以看出,Promise主要作用于外部,通过then方法,存储逻辑处理函数,而Deferred用于内部,改变Promise的状态,并调用相应的处理函数。
拿node中读取文件为例,读取一个文件,经过Promsie/Deferred封装后,会变成如下形式:
const fs = require("fs");
function read(path) {
let deferred = new myDeferred();
fs.readFile(path,(err,data) => {
if(err) {
deferred.reject(err);
return;
}
deferred.resolve(data);
})
return deferred.promise;
}
// 异步调用一个文件
rea