理解Promise

异步定义

当一个操作开始执行后,主程序无需等待它的完成,可以继续向下执行。此时该操作可以跟主程序同时(并发)执行。这种操作我们就称之为异步操作。 通常当操作完成时,会执行一个我们事先设定好的回调函数来做后续的处理。

我们常见的异步操作例如:

  • 添加定时器 setTimeout/setInterval
  • 执行某个动画 animate
  • 发起网络请求 request

问题

如果有很多异步操作需要顺序执行,就会产生所谓的“回调地狱”:

1
2
3
4
5
6
7
8
9
ajaxA(function( ){
ajaxB(function( ){
ajaxC(function( ){
ajaxD(function( ){
......
});
});
});
})

这种代码不管是写起来还是读起来都比较烦人。

我们来看下经过Promise改造后的样子(伪代码):

1
2
3
4
new Promise(ajaxA)
.then(ajaxB)
.then(ajaxC)
.then(ajaxD);

使用

示例感受下:

1
2
3
4
5
6
7
new Promise(resolve=>{
ajax("/pay/post", data => resolve() );
}).then(resolve=>{
ajax("/order/fix", data => {
//处理数据
})
})

上面的代码使用了ES6的箭头函数,虽然大大简化了代码的写法,但对于初级程序猿来讲极不友好;

我们把代码还原成ES5的样子:

1
2
3
4
5
6
7
8
9
new Promise(function(resolve){
ajax("/pay/post",function(data){
resolve();
})
}).then(function(){
ajax("/order/fix",function(data){

})
})

原理

  1. Promise是怎么知道第一个函数什么时候结束的? 然后再开始执行下一个?

    Promise并没有那么神奇,它并不能知道我们的函数什么时候结束,
    在ajax请求结束执行回调的时候,
    我们调用了一个resolve()函数,这句代码非常的关键.
    这其实就是在通知Promise,当前这个函数结束啦,
    你可以开始执行下一个。 这时Promise就会去执行then里面的函数了。

如果我不调用resolve()这个方法,Promise就不知道这个函数有没有结束,那么then里面的函数就不会执行,也就是说我的第二个请求就永远不会发送了

  1. Promise基本结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    new Promise(函数1).then(函数2);

    我们把函数1和函数2都以参数形式传给了一个Promise对象,
    所以接下来函数12都会由这个Promise对象控制,
    简单的说,函数1和函数2都会由Promise对象来执行。
    所以在函数1执行时,参数也当然是由Promise对象传递进去的。

    new Promise(function(resolve){
    //resolve是Promise对象在调用函数时传入的参数
    }).then(函数2);
  2. resolve函数作用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    以参数传进来的resolve函数, 就好像一个对讲机,
    当我们的异步任务要结束时,通过对讲机 来通知Promise对象。
    也就是调用resolve方法:

    new Promise(function(resolve){
    ajax("/pay/post",function(data){
    //当请求结束时,通过调用resolve方法,通知Promise对象,该任务已完成
    resolve(); //收到通知后,Promise会立刻开始函数2的执行
    })
    }).then(函数2);
  3. 如果我有ajaxA、ajaxB、ajaxC三个异步任务,想按照先A后B再C的顺序执行,像这样写行吗?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    new Promise(function(resolve){
    ajax("/AAA", function(){
    resolve(); //通知Promise该任务结束
    })
    }).then(function(resolve){
    ajax("/BBB", function(){
    resolve();//通知Promise该任务结束
    })
    }).then(function(){
    ajax("/CCC", function(){ //.... })
    })

上面的这种写法是不对的。
Promise的中文含义是“承诺”,
则意味着,每一个Pormise对象,代表一次承诺
而每一次承诺,只能保证一个任务的顺序,也就是说
new Promise(A).then(B); 这句话表示, 只能保证A和B的顺序

一旦A执行完,B开始后,这次承诺也就兑现了,Promise对象也就失效了
那如果还有C呢? 我们就必须在函数B中,
重新创建新的Promise对象,来完成下一个承诺,具体的写法就像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise(函数1(resolve){
ajaxA("xxxx", function(){
resolve();//通知Promise该任务结束
})
}).then(函数2(){
//在函数2开始运行后,第一次创建的Promise对象完成使命,已经不能再继续工作。
//此时,我们创建并返回了新的Promise对象
return new Promise(function(resolve){
ajaxB("xxxx", function(){
resolve();//通知新的Promise对象该任务结束
})
})
}).then(函数3(){ //尽管这里使用了链式调用,但负责执行函数3的,已经是新的Promise对象了
// 如果,我们还有ajaxD需要顺序调用
// 那就必须在这里重新new Promise()对象了
ajaxC("xxx", function(){ })
})
  1. 其他功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    例如: 如果我有 A,B,C 三个异步任务,ABC同时开始执行
    当A,B,C三个任务全部都结束时,执任务D,
    传统方法实现起来就比较复杂,Promise就非常简单,就像这样:

    Promise.all([new Promise(A), new Promise(B), new Promise(C)])
    .then(function(){
    D();
    });

    如果我希望A,B,C 其中任意一个任务完成,
    就马上开始任务D,该怎么做?
    Promise.race([new Promise(A), new Promise(B), new Promise(C)])
    .then(function(){
    D();
    });