我们本次要了解一下 es6 中的 Promise 对象

then()

在 ES6 中函数有一个很明显的区别就是一些异步函数中不再是使用回调函数callback(),而是then() 方法来进行回调后的处理,这里then()就是我们今天要说的Promise()对象所包含的方法 举个例子: 使用回调函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}

function doSomething(success,error){
  let num = Math.random() * 10;
  console.log('result is :' + num);

  if(num > 5){
    success('yes');
  }else{
    error('no')
  }
}

doSomething(successCallback, failureCallback);

使用 Promise 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
function successCallback(result) {
  console.log("It succeeded with " + result);
}

function failureCallback(error) {
  console.log("It failed with " + error);
}

function doSomething(standard){
  return new Promise((resolve,reject) => {
    let num = Math.random() * 10;
    console.log('result is :' + num);

    if(num > standard){
      resolve('yes');
    }else{
      reject('no')
    }
  })
}

doSomething(5).then(successCallback).catch(failureCallback);

resolvereject 这两个函数是系统提供的内置函数,负责改变Promise的状态 这样的写法和之前没什么大变化,但是代码的表达方式更清晰了,而不是在一层层的 callback 中寻找执行 的节点, then()方法也可以有第二个参数,第二个参数代表着失败时要执行的函数,以上面的例子来说:

1
doSomething(5).then(successCallback,failureCallback);

这样写也是没差,但是使用 catch() 我感觉是一种更优的选择,原因我后面再说

这里是一个图片异步加载的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<div id="content"></div>

<script type="text/javascript">

  let url = 'http://wx3.sinaimg.cn/thumb150/9ccb8305ly1fd09eeednzg208q07wh0l.gif';
  loadImageAsync(url).then(function(res){
    let content = document.getElementById('content');
    content.appendChild(res);
  }).catch(function(error){
    let content = document.getElementById('content');
    content.innerHTML = error;
  });



  function loadImageAsync(url){
    return new Promise(function(resolve,reject){
      var image = new Image();

      image.onload = function(){
        console.log('图片已加载');
        resolve(image);
      }

      image.onerror = function(){
        console.log('图片加载失败');
        reject(new Error('load image :' + url + ' false'))
      }

      console.log('加载图片');
      image.src = url;
    })
  }

</script>

接下来是一个实现了 ajax 请求的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//这里.then()中可以返回一个全新的 Promise 对象,从而可以链式调用
getMethod("/test.json")
.then(function(response){
  console.log("收到json1",response);
  return getMethod("/test.json");
})
.then(function(response){
  console.log("收到json2",response);
})
.catch(function(error){
  console.error("请求错误",error);
});

function getMethod(url){
  var promise = new Promise(function(resolve,reject){
    var client = new XMLHttpRequest();
    client.open("GET",url);
    client.onreadystatechange =  handler;
    client.responseType = "json";
    client.setRequestHeader("Accept","application/json");
    client.send();

    function handler(){
      if(this.readyState !== 4){
        return;
      }

      if(this.status === 200){
        resolve(this.response);
      }else{
        reject(new Error(this.statusText));
      }
    }
  })

  return promise
}

axios也是支持 promise 的 API,它的解耦做的很不错,看它的源码也给我带来了很大的收获

当运行完这三个例子以后现在可以对 Promise 对象有一个大体的印象:

  • Promise 中有两个自带的函数resolvereject,分别代表该对象成功和失败两个结果需要处理的状态
  • 在 Promise 中,只有三种状态Pending,Resolved,Rejected,而且同一时间只会出于一种状态中
  • Promise 中的状态改变不可逆,一旦发生就不能再修改
  • 在 Promise 中的 resolve(param) 中的参数将传到 catch(resolve[,reject])中,reject(param)同理
  • then()代表着 Promise 的回调处理,而且返回一个新的 promise 对象,和原来的不是同一个

catch()

还是刚才 ajax 的运行例子,不过这次我们修改点东西

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
var allow = false;
function getMethod(url){
  var promise = new Promise(function(resolve,reject){

    if(allow){
      console.log('第二次执行');
      allow = !allow;
      reject("第二次执行被拒");
    }else {
      console.log('第一次执行');
      allow = !allow;
      reject("第一次执行被拒");
    }



    var client = new XMLHttpRequest();
    client.open("GET",url);
    client.onreadystatechange =  handler;
    client.responseType = "json";
    client.setRequestHeader("Accept","application/json");
    client.send();

    function handler(){
      if(this.readyState !== 4){
        return;
      }


      if(this.status === 200){
        resolve(this.response);
      }else{
        reject(new Error(this.statusText));
      }
    }
  })

  return promise
}

getMethod("/test.json")
.then(function(response){
  console.log("收到json1",response);
  return getMethod("/test.json");
})
.then(function(response){
  console.log("收到json2",response);
})
.catch(function(error){
  console.log("请求错误",error);
});

通过一个变量来控制是否进行 ajax 请求

从这个例子我们可以看出来reject抛出的错误是有冒泡性质的,可以统一通过最后的.catch()一起捕获, 这也是为什么我刚才说不推荐在then(reslve,reject)中使用错误的回调处理,集中在最后的catch()中进行处理 这样也让代码看起来更直白一点