标签:
本篇,主要普及promise的用法。
一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心。在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路。
近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。
在callback的模型里边,我们假设需要执行一个异步队列,代码看起来可能像这样:
loadImg(‘a.jpg‘, function() {
loadImg(‘b.jpg‘, function() {
loadImg(‘c.jpg‘, function() {
console.log(‘all done!‘);
});
});
});
这也就是我们常说的回调金字塔,当异步的任务很多的时候,维护大量的callback将是一场灾难。当今Node.js大热,好像很多团队都要用它来做点东西以沾沾“洋气”,曾经跟一个运维的同学聊天,他们也是打算使用Node.js做一些事情,可是一想到js的层层回调就望而却步。
好,扯淡完毕,下面进入正题。
Promise可能大家都不陌生,因为Promise规范已经出来好一段时间了,同时Promise也已经纳入了ES6,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。
所谓Promise,字面上可以理解为“承诺”,就是说A调用B,B返回一个“承诺”给A,然后A就可以在写计划的时候这么写:当B返回结果给我的时候,A执行方案S1,反之如果B因为什么原因没有给到A想要的结果,那么A执行应急方案S2,这样一来,所有的潜在风险都在A的可控范围之内了。
上面这句话,翻译成代码类似:
var resB = B();
var runA = function() {
resB.then(execS1, execS2);
};
runA();
只看上面这行代码,好像看不出什么特别之处。但现实情况可能比这个复杂许多,A要完成一件事,可能要依赖不止B一个人的响应,可能需要同时向多个人询问,当收到所有的应答之后再执行下一步的方案。最终翻译成代码可能像这样:
var resB = B();
var resC = C();
...
var runA = function() {
reqB
.then(resC, execS2)
.then(resD, execS3)
.then(resE, execS4)
...
.then(execS1);
};
runA();
在这里,当每一个被询问者做出不符合预期的应答时都用了不同的处理机制。事实上,Promise规范没有要求这样做,你甚至可以不做任何的处理(即不传入then的第二个参数)或者统一处理。
好了,下面我们来认识下Promise/A+规范:
then
方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致可以看到,Promise规范的内容并不算多,大家可以试着自己实现以下Promise。
以下是笔者自己在参考许多类Promise库之后简单实现的一个Promise,代码请移步promiseA。
简单分析下思路:
构造函数Promise接受一个函数resolver
,可以理解为传入一个异步任务,resolver接受两个参数,一个是成功时的回调,一个是失败时的回调,这两参数和通过then传入的参数是对等的。
其次是then的实现,由于Promise要求then必须返回一个promise,所以在then调用的时候会新生成一个promise,挂在当前promise的_next
上,同一个promise多次调用都只会返回之前生成的_next
。
由于then方法接受的两个参数都是可选的,而且类型也没限制,可以是函数,也可以是一个具体的值,还可以是另一个promise。下面是then的具体实现:
Promise.prototype.then = function(resolve, reject) {
var next = this._next || (this._next = Promise());
var status = this.status;
var x;
if(‘pending‘ === status) {
isFn(resolve) && this._resolves.push(resolve);
isFn(reject) && this._rejects.push(reject);
return next;
}
if(‘resolved‘ === status) {
if(!isFn(resolve)) {
next.resolve(resolve);
} else {
try {
x = resolve(this.value);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;
}
if(‘rejected‘ === status) {
if(!isFn(reject)) {
next.reject(reject);
} else {
try {
x = reject(this.reason);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;