JavaScript 的 Promises 商业模式,许多人都不认知,那时话虽如此题主的那个试题,我在此撷取一则胡须哥的控制技术贴给我们,期望对我们认知那个习题略有协助。
原作:Barret Lee 原镜像:http://www.cnblogs.com/hustskyking/p/promise.html
原原文:对个人网志Ajax 出现的时候,吹来了一阵阵触发器之风,现在 Nodejs 火热,又一阵阵触发器狂风暴雨刮了回来。市场需求是愈来愈严苛,使用者对操控性的要求也是愈来愈高,接踵而至的是网页触发器操作方式成分股般增长,假如不能正确的contacts方法论,他们就会陷于无穷无尽反弹冥界中。
ECMAScript 6 已经将触发器操作方式列入了规范化,当代应用程序也内建了 Promise 第一类供他们进行触发器程式设计,那么此时此刻,急忙自学自学 Promise 的外部基本原理吧!
第二章 介绍 Promise
一、情景重现
由于 javascript 的Renderscript物理性质,他们要等候上一个该事件继续执行顺利完成就可以处置下一步棋,如下表所示:
// DOM ready后继续执行
$(document).ready(function(){
2, function(data){
// 构筑 DOMString
makeHtml(tpl, data, function(str){
// 填入到 DOM 中
$(obj).html(str);
});
});
});
});
为的是增加井字统计数据的读取,他们将一些模版和大部份统计数据都放到服务端,当使用者操作方式某一按键时,需要将模版和统计数据堆叠起来插入到 DOM
中,那个过程还要在 DOMReady
后就可以继续执行。这种情况是极为常用的,假如
触发器操作方式
再多些,整座标识符的对齐让人看著很不难受,为的是典雅地处置那个问题,ECMAScript 6
导入了 Promise 的概念,目前一些当代应用程序已经支持这些新东西了!
二、模型
为的是让标识符流程更加清晰,他们假想着能够按照下面的流程来跑程序:
new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();
先将要事务按照继续执行顺序依次 push 到事务队列中,push 完了后再通过 resolve 函数启动整座流程。
整座流程的操作方式模型如下表所示:
promise(ok).then(ok_1).then(ok_2).then(ok_3).reslove(value)——+
| | | | |
| | | | +=======+ |
| | | | | | |
| | | | | | |
+———|———-|———-|——–→ ok() ←——+
| | | | ↓ |
| | | | ↓ |
+———-|———-|——–→ ok_1()|
| | | ↓ |
| | | ↓ |
+———-|——–→ ok_2()|
| | ↓ |
| | ↓ |
+——–→ ok_3()—–+
| | |
| | ↓
@ Created By Barret Lee +=======+ exit
在 resolve 之前,promise 的每一个 then 都会将
反弹函数压入队列,resolve 后,将 resolve 的值送给队列的第一个函数,第一个函数继续执行完毕后,将继续执行结果再送入下一个函数,依次继续执行完队列。一连串下来,一气呵成,没有丝毫间断。
三、简单的封装
假如了解 Promise,可以移步下方,看看对 Promise 的封装:
Github:
https://github.com/barretlee/myPromiseDEMO:
http://barretlee.github.io/myP
romise/index.html假如还不是很介绍,可以往下阅读全文,介绍一二。
第二章 Promise 基本原理
一、什么是 Promise ?
那么,什么是 Promise ?
Pr后再将拼合的统计数据填入到 DOM 中,这里他们将整座程序分解成多个事务:
↓
事务三: 拼合后填入到 DOM
在事务一结束之前,也就是
模版标识符
从服务器拉取回来之前,事务二和事务三都处 pending 状态,他们要等候上一个事务结束。而事务一结束后会将自身状态标记为 resolved,并把该事务中处置的结果移交给事务二继续处置(当然,这里假如没有统计数据返回,事务二就不会获得上一个事务的统计数据),依次类推,直到最后一个事务操作方式结束。
么后续的大部份事务都会将自己标记为 rejected,其标记理由(reason)就是出错事务的报错信息(那个报错信息可以使用 try…catch 来捕获,也可以通过程序自身来捕获,如 ajax 的 onerror 该事件、ajax 返回的状态码为 404 等)。
小结:Promise 就是一个事务的管理器。他的作用就是将各种内嵌反弹的事务用流水形式表达,其目的是为的是简化程式设计,让标识符方法论更加清晰。
由于整座程序的实现比较难认知,对于 Promise,他们将分为两部分阐述:
无错误传递的 Promise,也就是事务不会因为任何原因中断,事务队列中的事项都会被依次处置,此过程中 Promise 只有 pend响下一个事务,则下一个事务也会 rejected,假如不会,下一个事务可以正常继续执行,依次类推。
二、无错误传递的 Promise(简化版的 Promise)
首先,他们需要用一个变量(status)来标记事务的状态,然后将事务(affair)也保存到 Promise 第一类中。
var Promise = function(affair){
this.state = “pending”;
this.affair = affair || function(o) { return o; };
this.allAffairs = [];
};
Promise 有两个重要的方法,一个是 then,另一个是 resolve:
then,将事务添加到事务队列(allAffairs)中resolve,开启流程,让整座操作方式从第一个事务开始继续执行
在操作方式事务之前,他们会先把各种事务依次放入事务队列中,这里会用到 then 方法:
Promise.prototype.then = function (nextAffair){
var promise = new Promise();
if (this.state == ‘resloved’){
// 假如当前状态是已顺利完成,则那个事务将会被立即继续执行
return this._fire(promise, nextAffair);
}else{
// 否则将会被加入队列中
return this._push(promise, nextAffair);
}
};
假如整座操作方式已经顺利完成了,那 then 方法送进的事务会被立即继续执行,
Promise.prototype._fire = function (nextPromise, nextAffair){
var nextResult = nextAffair(this.result);
if (nextResult instanceof Promise){
nextResult.then(function(obj){
nextPromise.resolve(obj);
});
}else{
nextPromise.resolve(nextResult);
}
return nextPromise;
};
被立即继续执行后会返回一个结果,那个结果会被传递到下一个事务中作为原料,但是这里需要考虑两种情况:
触发器,假如那个结果也是一个 Promise,则需要等待那个 Promise 继续执行完毕再将最终的结果传到下一个事务中。同步,假如那个结果不是 Promise,则直接将结果传递给下一个事务。第一种情况还是比较常用的,比如他们在一个事务中有一个子事务队列需要处置,此时要等候
子事务顺利完成就可以回到主事务队列中。
Promise.prototype.resolve = function (obj){
if (this.state != ‘pending’) {
throw ‘流程已顺利完成,不能再次开启流程!’;
}
this.state = ‘resloved’;
// 继续执行该事务,并将继续执行结果寄存到 Promise 管理器上
this.result = this.affair(obj);
for (var i = 0, len = this.allAffairs.
length; i < len; ++i){
// 往后继续执行事务
var affair = this.allAffairs[i];
this._fire(affair.promise, affair.affair);
}
return this;
};
resolve 接受一个参数,那个统计数据是交给第一个事务来处置的,因为第一个事务的启动可能需要点原料,那个统计数据就是原料,它也可以是空。该事物处置完毕后,将操作方式结果(result)寄存在 Promise 第一类上,方便引用,然后将结果(result)作为原料送入下一个事务。依次类推。
他们看到 then 方法中还调用了一个 _push ,那个方法的作用是将事务推进
事务管理器(Promise)。
Promise.prototype._push = function (nextPromise, nextAffair){
this.allAffairs.push({
promise: nextPromise,
affair: nextAffair
});
return nextPromise;
};
以上操作方式,他们就实现了一个简单的事务管理器,可以测试下下面的标识符:
// 初始化事务管理器
var promise = new Promise(function(data){
console.log(data);
return 1;
});
// 添加事务
promise.then(function(data){
console.log(data);
return 2;
}).then(function(data){
console.log(data);
return 3;
}).then(function(data){
console.log(data);
console.log(“end”);
});
// 启动事务
promise.resolve(“start”);
可以看到依次输出的结果为:
> start
> 1
> 2
> 3
> end
由于上述实现极为简陋,链式调用没做太好的处置,请读者自行完善。
下面是一个触发器操作方式演示:
var promise = new Promise(function(data){
console.log(data);
return “end”;
});
promise.then(function(data){
// 这里需要返回一个 Promise,让主事务切换到子事务处置
return (function(data){
// 创建一个子事务
var promise = new Promise();
setTimeout(function(){
console.log(data);
// 一秒后才启动子事务,模拟触发器延时
promise.resolve();
}, 1000);
return promise;
})(data);
});
promise.resolve(“start”);
可以看到依次输出的结果为:
> start
> end (1s后输出)
将函数写的稍微好看点:
function delay(data){
// 创建一个子事务
var promise = new Promise();
setTimeout(function(){
console.log(data);
// 一秒后才启动子事务,模拟触发器延时
promise.resolve();
}, 1000);
return promise;
}
// 主事务
var promise = new Promise(function(data){
console.log(data);
return “end”;
});
promise.then(delay);
promise.resolve(“start”);
三、包含错误传递的 Promise
真的很羡慕你能看到这么详细的文章,当然,后面会更加精彩!
没有错误处置的 Promise 只能算是一个半成品,虽说可以通过在最外层加一个 try..catch
来捕获错误,但没法具体定位是哪个事务发生的错误。并且这里的错误不仅仅包含 JavaScript Error,还有诸如 ajax 返回的 data code 不是 200 的情况等。
先看一个应用程序内建 Promise 的实例(该标识符可在当代应用程序下运行):
new Promise(function(resolve, reject){
resolve(“start”);
}).then(function(data){
console.log(data);
throw “error”;
}).catch(function(err){
console.log(err);
return “end”;
}).then(function(data){
console.log(data)
});
Promise 的反弹和 then 方法都是接受两个参数:
new Promise(function(resolve, reject){
// …
});
promise.then(
function(value){/* code here */},
function(reason){/* code here */}
);
事务处置过程中,假如有值返回,则作为 value,传入到 resolve 函数中,若有错误产生,则作为 reason 传入到
reject
函数中处置。
在初始化 Promise 第一类时,若传入的反弹中没有继续执行 resolve 或者 reject,这需要他们主动去启动事务队列。
promise.resolve();
promise.reject();
上面两种都是可以启动一个队列的。这里跟第二章第二节的 resolve 函数用法类似。Promise 第一类还提供了 catch 函数,起用法等价于下面所示:
promise.catch();
// 等价于
promise.then(null, function(reason){});
还有两个 API:
后续再讲。先看看那个有错误处置的 Promise 是怎样实现的。
function Promise(resolver){
this.status = “pending”;
this.value = null;
this.handlers = [];
this._doPromise.call(this, resolver);
}
_doPromise 方法在实例化 Promise 函数时就继续执行。假如送入的反弹函数 resolver 中已经 resolve 或者 reject 了,程序就已经启动了,所以在实例化的时候就开始判断。
_doPromise: function(resolver){
var called = false, self = this;
try{
resolver(function(value){
// 假如没有 call 则继续,并标记 called 为 true
!called && (called = !0, self.resolve(value));
}, function(reason){
// 同上
!called && (called = !0, self.reject(reason));
});
} catch(e) {
// 同上,捕获错误,传递错误到下一个 then 事务
!called && (called = !0, self.reject(e));
}
},
只要 resolve 或者 reject 就会标记程序 called 为 true,表示程序已经启动了。
resolve: function(value) {
try{
if(this === value){
throw new TypeError(‘流程已顺利完成,不能再次开启流程!’);
} else {
// 假如还有子事务队列,继续继续执行
value && value.then && this._doPromise(value.then);
}
// 继续执行完了后标记为顺利完成
this.status = “fulfilled”;
this.value = value;
this._dequeue();
} catch(e) {
this.reject(e);
}
},
reject: function(reason) {
// 标记状态为出错
this.status = “rejected”;
this.value = reason;
this._dequeue();
},
可以看到,每次 resolve 的时候都会用一个 try..catch 包裹来捕获未知错误。
_dequeue: function(){
var handler;
// 继续执行事务,直到队列为空
while (this.handlers.length) {
handler = this.handlers.shift();
this._handle(handler.thenPromise, handler.onFulfilled, handler.onRejected);
}
},
无论是 resolve 还是 reject 都会让程序往后奔流,直到结束大部份事务,所以这两个方法中都有 _dequeue 函数。
_handle: function(thenPromise, onFulfilled, onRejected){
var self = this;
setTimeout(function() {
// 判断下次操作方式采用哪个函数,reject 还是 resolve
var callback = self.status == “fulfilled”
? onFulfilled
: onRejected;
// 只有是函数才会继续反弹
if (typeof callback === ‘function’) {
try {
self.resolve.call(thenPromise, callback(self.value));
} catch(e) {
self.reject.call(thenPromise, e);
}
return;
}
// 否则就将 value 传递给下一个事务了
self.status == “fulfilled”
? self.resolve.call(thenPromise, self.value)
: self.reject.call(thenPromise, self.value);
}, 1);
},
那个函数跟上一节提到的 _fire 类似,假如 callback 是 function,就会进入子事务队列,处置完了后退回到主事务队列。最后一个 then 方法,将事务推进队列。
then: function(onFulfilled, onRejected){
var thenPromise = new Promise(function() {});
if (this.status == “pending”) {
this.handlers.push({
thenPromise: thenPromise,
onFulfilled: onFulfilled,
onRejected: onRejected
});
} else {
this._handle(thenPromise, onFulfilled, onRejected);
}
return thenPromise;
}
假如第二节没有认知清楚,这一节也会让人头疼,这一部分讲的比较粗糙。
第三章 触发器程式设计
一、jQuery 中的 Defferred 第一类
或许你在面试的时候,有面试官问你:
$.ajax() 继续执行后返回的结果是什么?
在 jQuery1.5 版本就已经导入了 Defferred 第一类,当时为的是导入那个东西,整座 jQuery 都被重构了。Defferred 跟 Promise 类似,它表示一个还未顺利完成任务的第一类,而 Promise 确切的说,是一个代表未知值的第一类。
$.ajax({
url: url
}).done(function(data, status, xhr){
//…
}).fail(function(){
//…
});
回忆下第二章第一节中的 Promise,是不是如出一辙,只是 jQuery 还提供了更多的语法糖:
$.ajax({
url: url,
success: function(data){
//…
},
error: funtion(){
//…
}
});
他允许将 done 和 fail 两个函数的反弹放到 ajax 初始化的参数 success 和 fail 上,其基本原理还是一样的,同样,还有这样的东西:
$.when(taskOne, taskTwo).done(function () {
console.log(“都继续执行完毕后才会输出我!”);
}).fail(function(){
console.log(“只要有一个失败,就会输出我!”)
});
当 taskOne 和 taskTwo 都顺利完成后才执行 done 反弹,那个应用程序内建的 Promise 也有对应的函数:
Promise.all([true, Promise.resolve(1), …]).then(function(value){
//….
});
应用程序内建的 Promise 还提供了一个 API:
Promise.race([true, Promise.resolve(1), …]).then(function(value){
//….
}, function(reason){
//…
});
只要 race 参数中有一个 resolve 或者 reject,then 反弹就会出发。
二、基于事件响应的触发器模型
@朴灵 写的 EventProxy 就是基于该事件响应的触发器模型,按理说,那个实现的方法论是最清晰的,不过标识符量稍微多一点。
function taskA(){
setTimeout(function(){
var result = “A”;
E.emit(“taskA”, result);
}, 1000);
}
function taskB(){
setTimeout(function(){
var result = “B”;
E.emit(“taskB”, result);
}, 1000);
}
E.all([“taskA”, “taskB”], function(A, B){
return A + B;
});
我没有看他的源码,但是想想,应该是那个方法论。只需要在消息中心管理各个 emit 以及消息注册。这里的错误处置值得思考下。
在半年前,也写过一则关于触发器程式设计的文章:
JavaScript触发器程式设计基本原理,感兴趣的可以去读一读。
第四章 小结
文章比较长,主要解说了 Promise 的应用情景和实现基本原理,假如你能够顺畅的读完全文并且指出文中的一些错误,说明你已经悟到了。