es6 - Promise

es6 promise与异步编程

对于一些还不具有大量编程经验的朋友来讲,promise多是es6比较难以掌握的点。首先是不少名词,好比Promises,es6 Promise, 回调函数(callback),Promise/A+,异步编程等。下面就首先介绍下这些名词的含义和区别。es6

所谓异步编程中的异步是相对于同步的概念的。js是单线程的语言,同一时间只能作一件事,为了指定一些稍后要执行的代码,咱们须要异步。在客户端,主要的异步方式有事件,setTimeout,Ajax等。Node的发展大大扩展了js语言的边界,咱们知道,Node使用非阻塞IO模型,它使用回调函数模式来实现异步编程。好比:编程

readFile('example.txt', function(err, contents){
    if(err){ throw err; }
    console.log(contents);
});
console.log('Hi!');

上面代码中readFile的第二个参数就是回调函数。它会在读取完example.txt后被添加到执行队列中。上面代码的执行顺序是--执行readFile函数,在遇到读取文件时暂停,打印"Hi",读取文件结束后将回调添加到做业队列中,执行回调函数,打印contents。segmentfault

原本呢,使用回调函数是可以完成异步编程的。可是随着代码的逻辑越复杂,这种异步编程方式愈来愈难以阅读和追踪程序错误,因此发展出了Promises规范来完成异步编程。数组

Promises是一系列异步编程规范的统称。咱们须要了解的是其中的Promise/A+规范。es6经过Promise这个内建对象实现了该规范。因此咱们可使用es6中的Promise对象来进行异步编程。promise

下面将对es6中的Promise对象进行介绍。至于jQuery中延迟对象$.deferred(),根据规范本身实现promise和ES7的Async/Await异步方式等更多内容,后面会专门写一篇文章进行介绍。异步

语法

Promise的3种状态

一个promise实例有3种状态,分别是:异步编程

  • pending -- 挂起,表示Promise结果还未知。
  • fulfilled -- 已完成, 表示Promise成功完成。
  • rejected -- 已拒绝,表示Promise未成功结束。

promise处于这3种状态中的一种,而且能够由pending状态变为fulfilled状态,或由pending变为rejected状态。反之则不行。函数

为了便于理解,下面将经过一个生活化的例子,来解释什么是Promise?学习

Promise是允诺的意思。它就是一个关于未发生的事情的承诺。好比:this

你订了一份烧烤,店家说半个小时内送到,这就是一个Promise。如今,这个Promise尚未发生,因此可能半个小时内配送成功或者失败。对此,你预备了两种处理方式:成功 -- 美滋滋的吃烧烤,失败 -- 去楼下店里吃。

在半个小时内,这个Promise处于pending状态,你正常上网,撸代码。一段时间后,这个promise就有告终果。是成功(fulfilled)或者失败(rejected)。根据这个结果,你以前的两种处理方式就会相应执行。这就是promise。

对应的代码以下:

let promise = new Promise(function(resolve, reject){
    //等待店家送来中...
    let result = '配送成功'? true : false;
    if(result){
        resolve(value);
    }else{
        reject(reason);
    }
});

promise.then(function(value){
    //美滋滋吃烧烤...
    //value为上面resolve()中传递的值, 好比共100块钱。
}, function(reason){
    //叫上隔壁老王去楼下吃...
    //reason为上面reject()的传递的缘由,好比烤糊了...
});

上面代码就是经过promise异步编程的代码。这里要注意的是Promise构造函数接收一个函数做为参数,函数内部是异步的逻辑。这个函数接收两个参数:resolve和reject。resolve()能够把promise推向fulfilled状态,reject()能够把promise推向rejected状态。

promise有个then方法,用于处理promise成功或失败后的逻辑。then有两个参数:
参数1为promise成功时执行的函数,该函数的参数value对应于上面resolve(value)中的value值;
参数2为promise失败时执行的函数,该函数的参数reason对应于reject(reason)中的reason值,表示失败的缘由。
一旦promise有告终果(成功或失败),就会执行对应then中的函数。

经过Promise处理Ajax的例子

Ajax是客户端最经常使用的异步编程场景,下面一个例子演示了使用Promise进行Ajax操做的代码。

function getData(method, url){
  let promise = new Promise(function(resolve, reject){
    let xmlHttp = new XMLHttpRequest();
    xmlHttp.open(method, url);
    xmlHttp.send();
    xmlHttp.onload = function () {
      if (this.status == 200 ) {
        resolve(this.response);
      } else {
        reject(this.statusText);
      }
    };
    xmlHttp.onerror = function () {
      reject(this.statusText);
    };
  })
  return promise;
}

getData('get','www.xxx.com').then(successFun, failFun);

function successFun(value){
  //Ajax成功处理函数...
}
function failFun(reason){
  //Ajax失败处理函数...
}

建立一个已决的Promise

前面的例子promise建立时,promise都处于pending状态,根据异步操做的结果将promise推向成功或失败状态。

Promise类型有两个静态方法Promise.resolve(value),Promise.reject(reason)能够分别建立已是fulfilled和已是rejected状态的promise。
好比:

let promise = Promise.resolve(44);
promise.then(function(value){
  console.log('fulfilled', value);
})

上面代码promise在被建立出来时,已是fulfilled状态,接下来会直接将then中的回调函数加入到做业队列中,等待做业队列中前面的任务完成后执行该函数。

这里传入Promise.resolve(value)和Promise.reject(reason)中的参数和以前Promise构造是对应的参数是同样的。

Promise.prototype.then()和Promise.prototype.catch()

上面已经演示过promise实例上then方法的用法,每个promise实例还具备catch方法。

catch()方法只处理reject的状况,他的行为与调用Promise.prototype.then(undefined, onRejected)相同。好比:

let p = new Promise(function(resolve, reject){
    //...
    reject(new Error('something wrong!'))
})
p.catch(function(reason){
    //拒绝
})

上面catch方法中的回调在promise被reject时调用。

then()和catch()的返回值

每次对then()或catch()的调用都会返回另外一个promise,这也是不少代码能够写成相似链式调用的缘由。好比:

let p1 = new Promise(function(resolve, reject){
  resolve(42);
});
let p2 = p1.then(function(value){
  console.log(value);
})

p2.then(function(){
  console.log("Finished");
}, function(){
    console.log('something wrong!');
});

p1 == p2 // false,注意:p1.then()会返回一个新的promise,因此p1与p2并不相等

//能够写成链式调用的形式,好比
p1.then(function(value){
  console.log(value)
}).then(function(){
  console.log('do something');
}).then(function(){
  console.log('Finished');
})

在上面代码中,p1.then()返回了一个promise为p2, 那么p2的状态和p1之间有什么关系呢?

更具体一点说,当p1变为fulfilled时,p1.then()返回的p2是什么状态呢?两者有什么联系呢?

p2的行为与p1.then()中回调函数的返回值有关:

  • 若是then中的回调函数抛出一个错误,或者回调函数中调用reject(reason),那么then返回的Promise将会成为拒绝状态,而且将抛出的错误做为拒绝状态的回调函数的参数值。
  • 若是then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,而且将返回的值做为接受状态的回调函数的参数值。
  • 若是then中的回调函数返回一个已是接受状态的Promise,那么then返回的Promise也会成为接受状态,而且将那个Promise的接受状态的回调函数的参数值做为该被返回的Promise的接受状态回调函数的参数值。
  • 若是then中的回调函数返回一个已是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,而且将那个Promise的拒绝状态的回调函数的参数值做为该被返回的Promise的拒绝状态回调函数的参数值。
  • 若是then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,而且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
  • 若是then中的回调函数无显式的返回值,而且也没有调用reject(),那么返回的Promise为接收状态。

Promise.all()处理多个promise

Promise内建对象上的静态方法Promise.all()用于处理多个promise的状况。

Promise.all([promise1, promise2,...])返回一个promise的实例,接收一个promise组成的数组为参数。只有当数组内的promise都成功时,才会调用对应的then中的成功处理函数,只要有一个不成功,那么调用对应的拒绝处理函数。

依然使用前面那么订烧烤的例子,你不只订了烧烤,还在另外一家订了啤酒。打算等到烧烤和啤酒都配送成功后一块儿吃,美滋滋~~。好比:

Promise.all([订烧烤,订啤酒]).then(function(value){
    //吃烧烤,喝啤酒...
}, function(reason){
    //拒绝的缘由,烤糊了或者啤酒卖完了...
})

这里要注意的一点是,对于数组中的promise,只要有任一个promise为拒绝,那么就会当即执行then中的拒绝处理函数,并不会等待其余promise的结果。只有当全部promise的结果都成功时,才执行then中的成功处理函数。好比:

var p1 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log('A');
    resolve();
  }, 1000)
});
var p2 = Promise.reject(new Error('error'));
var p3 = new Promise(function(resolve, reject){
  setTimeout(function(){
    console.log('B');
    resolve();
  }, 0)
});
Promise.all([p1,p2,p3]).then(function(value){
  console.log('success!');
}, function(reason){
  console.log('failed');
})
//结果为failed B  A

因为p2为已拒绝状态的promise,因此Promise.all()当即变为拒绝状态,打印failed,p1和p2会继续执行,但对于Promise.all()的结果没有影响。

Promise.race()处理多个promise

Promise内建对象上的静态方法Promise.race()一样用于处理多个promise的状况。一样返回一个Promise,一样接收一个promise数组做为参数。

与all不一样的地方在于,数组中的promise就像在赛跑同样(race),而且只关心第一名的状况,只要有其中一个promise有告终果,Promise.race()的状态就会当即与该promise相同。
数组中其余promise继续执行,但对于Promise.race()的结果没有影响。

注意事项

  • 咱们构造promise实例的代码是当即执行的,而then方法中的回调函数是异步调用的,在promise的状态变为成功或拒绝时,才会把相应的处理函数添加到promise工做队列中。而且该函数会先于setTimeout执行。例如:
var promise = new Promise(function(resolve, reject){
  console.log('A');
  resolve('C');
})

console.log('B');

setTimeout(function(){
  console.log('D');
},0)

promise.then(function(value){
  console.log(value)
});
//打印A, B, C, D
  • 若是then方法中传入的参数被忽略,或者是非函数,好比:
p.then(function(value){
    //...
})
//或者
p.then(undefined, function(reason){
    //...
})

那么,相应的回调处理函数被忽略,then方法返回的promise会保留上一个promise的状态和参数。最典型的例子:

var p = new Promise(function(resolve, reject){
    reject(new Error('error'));
})
p.then(function(value){
    //...
}).then(function(value){
    //...
}).then(undefined, function(reason){
    console.log(reason);
})
//打印'error'

p的前两次then调用的拒绝处理函数被忽略,而后reject状态和错误信息就一直日后传递,直到被最后一次then调用捕获。

最佳实践

关于Promise的知识点不少,可是最经常使用的场景就是Ajax。好比:

function getData(method, url){
  var promise = new Promise(function(resolve, reject){
    //Ajax获取数据的代码...
    if(success){
      resolve(response)
    }else{
      reject(statusText)
    }
  })
  return promise;
}

getData('get','www.xxx.com').then(Fun1).then(Fun2).then(Fun3).catch(function(reason){
  //错误处理逻辑...
});

更多关于es6的内容,能够关注右侧个人专栏--学习ES6。

参考:
MDN Javascript Promise.
Promise介绍-基础篇.《深刻理解ES6》-- Promise与异步编程。

相关文章
相关标签/搜索