#4. 异步编程与Promise
##4.1 异步编程有什么不同
异步编程有什么样的特点呢?比如说你有一个异步方法a,如果想要在a执行完之后,再执行一个b:
a();
b();
这样基本是得不到期待的结果的,很可能a里面的内容没有执行完,b倒先做完了。
通常我们写异步调用,都是使用回调的方式,比如这样:
function a(success, fail) {
//监听某个事件,检测到成功,就调用success,检测到失败,就调用fail
}
a(function(data) {
console.log(data);
}, function(reason) {
console.log(reason);
});
但是回调有个缺点,如果几个操作是有依赖关系,比如b要等到a成功了再做,c要等到b成功了再做,那代码可能变成这样:
a(function(data) {
b(function(data1) {
c(function(data2) {
//c成功之后要做的事情
})
})
});
这个写法很令人头疼,才3个连环操作,代码就变成这样了,多一个操作就多一层,到最后代码基本没法看了。
现在我们的需求很明确:
- 按照什么顺序写的代码,我就希望它按照什么顺序执行
- 不要搞成上面那样的嵌套
再一次拔剑四顾心茫然,什么才是我们真正要的东西?如果语言本身书写和执行的顺序不能控制,那我们可以人为创造一个队列。我们联想到jQuery,它以链式表达式著称,比如:a().b().c()这样,如果我们只是这样,那这个代码还是不对,跟三个函数分开写没有任何区别,因为没有实现异步。
这好办,异步就是让后续的函数不立即执行,换言之,我只要把这些后续函数存在某个地方,然后按照在它们的先决条件执行完之后才拿出来执行,就可以了,如果这样呢?when(a).then(b).then(c);
这个看上去还可以,所以我们来打算实现它。
#4.2 Promise
先看看要怎么实现方法的顺序调用,一定是要有一个地方能存放这个调用序列的,比如说是个队列。
- 每次有新方法要执行的时候,如果当前正在执行其他方法,就先把这个方法放到队列尾部,否则立即执行,并且把当前状态置为执行中。
- 在执行完成之后,修改执行状态为空闲,从队列头取出下一个方法,如果非空继续执行这个方法。
好,我们开始写:
var queue = [];
var executing = false;
function then(foo) {
if (executing) {
queue.push(foo);
}
else {
foo(function() {
executing = false;
next();
});
}
}
function next() {
if (queue.length > 0) {
var foo = queue.shift();
foo(function() {
executing = false;
next();
});
}
}
然后看看怎么写:
then(a);
then(b);
then(c);
哎,不对啊,我们不是要这么写,而是要形成一个链呢。再来:
function ExecuteQueue() {
this.queue = [];
this.executing = false;
}
ExecuteQueue.prototype = {
then: function(foo) {
if (this.executing) {
this.queue.push(foo);
}
else {
var that = this;
foo(function() {
that.executing = false;
that.next();
});
}
return this;
},
next: function() {
if (this.queue.length > 0) {
var foo = this.queue.shift();
var that = this;
foo(function() {
that.executing = false;
that.next();
});
}
}
};
用的时候,这样:
var queue = new ExecuteQueue();
queue.then(a).then(b).then(c);
好像有那么回事了,但还缺一些东西,比如说,如果a出异常了,想要取消b和c的执行,怎么办?这里面的问题在什么地方呢?队列可以调度这些方法的执行过程,但是这些方法本身没法控制队列的状态。
function a() { var queue = new ExecuteQueue();
return queue;
}