Promise的含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
上面两段都是阮一峰那本教程上的原话,我觉得说的算是清楚的,不知道从什么时候开始有这种感觉:如果不清楚一个东西的定义,那我对这个东西就不能说了解,虽然看了之后照着样子也能用,可始终也不觉得踏实。所以了解一个东西之前,无论如何得知道它是什么,如果做到用自己的话尝试给东西下个定义,那大概就做好了第一步。
从上面两段话可以得到一些信息:Promise(泛指的话)是一种异步编程的解决方案;这种方案比传统的解决方案(回调函数和事件)更合理、强大;它已经由ES6写入标准,而且统一了用法;标准里提供 了Promise对象供我们使用,通过对这个对象的使用我们就可以实现这种Promise解决方案的异步编程。所以Promise又可以特指Promise对象,那下面说的主要就是这个Promise对象。
Promise对象
那这个对象怎么生成的?原生提供了Promise这个构造函数,通过这个构造函数就可以new 出一个Promise对象实例
var promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error)
}
})
Promise构造函数以一个函数为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve
的作用是将Promise对象的状态从pending变为成功,从而执行后面的then()
里的第一个参数函数;reject
的作用是将Promise对象的状态从pending变为失败,从而执行后面的catch
或者then()
的第二个参数函数。所以当异步操作成功时调用resolve(value)
将value
作为参数传给successFn
(成功时的回调函数,作为then()
的第一个参数)并调用之;当异步操作失败的时候调用reject(e)
并将e
作为参数传给failureFn
(失败时的回调函数,作为then()
的第二个参数或catch()
的参数)并调用之。
举个例子:
function timeout(ms1, ms2) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms1, ms1 + 'ms 后我会被打印出来')
setTimeout(reject, ms2, new Error(ms2 + 'ms 后我会被打印出来'))
});
}
timeout(3000, 4000).then((value) => {
console.log(value)
}).catch( (e) => console.log(e))
//3s后打印 `3000ms 后我会被打印出来`
这里如果传入的参数ms1>ms2时,就会调用catch((e) => console.log(e))
,因为在ms2 ms之后先调用了setTimeout(reject, ms2, new Error(ms2 + 'ms 后我会被打印出来'))
将Promise对象的状态由pending改为失败的状态了。
此外,除了new Promise()的方式来创建对象,我们还可以通过Promise.resolve
和Promise.reject
两个方法来快速创建一个Promise对象:
Promise.resolve('lalala').then(console.log)
上面代码会立即打印出'lalala',虽然目前不知道这种方式有什么用。
then()
上面说了then()
方法接受两个函数,一个是异步操作成功时的回调函数,一个是异步操作失败时的回调函数,两个都可选。那then()
方法的返回值是什么呢?then()
返回的是一个新的Promise对象,正因为此所以我们可以执行一系列的链式操作,但是其返回的Promise对象的行为与then()
中的回调函数的返回值有关,下面是MDN上的内容:
then方法返回一个Promise,而它的行为与then中的回调函数的返回值有关:
- 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
一一举例:
回调函数返回值为value的情况
Promise.resolve('la').then(value => value + value)
.then(value => console.log(value + value)) //打印lalalala
.then(console.log) //打印undefined
第一个then的回调函数返回值是'lala',第二个then的回调函数返回值是 undefined,所以每个then依次执行
回调函数抛出错误的情况
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'lalala')
});
}
timeout(1000).then(value => {throw new Error(value)})
.then(console.log, (e) => console.log(e.toString()))//打印Error:lalala
上面timeout()
返回的Promise对象1000ms后执行throw new Error(value)
,即then()
的回调函数抛错,然后这个错误被第二个then()
捕获,并调用 (e) => console.log(e.toString())
,说明第一个then()
返回的Promise对象因回调函数的抛错而变成失败状态
回调函数返回已经接受状态的Promise的情况
Promise.resolve('lala').then((value) => Promise.resolve(value + 'haha'))
.then(value => console.log(value + ': 我是从上一个then的回调函数中传过来的'))
//打印lalahaha: 我是从上一个then的回调函数中传过来的
第一个then()
的回调函数处理返回的是一个新的Promise对象,这个对象的状态是成功的,并且将resolve('lalahaha')
的参数传给下一个then()
的successFn()
回调函数返回已经拒绝状态的Promise的情况
Promise.resolve('lala').then((value) => Promise.reject(value + 'haha'))
.then(value => console.log(value + ': 我是从上一个then的回调函数中传过来的'))
.catch(value => console.log(new Error(value)))
//打印一个Error
跟第三种情况类似,不赘述
前面四种then()
的回调函数的返回情况几乎都是同步的操作,没有涉及到异步,所以比较简单。then()
的回调函数的返回的Promise对象又涉及到异步操作的情况才是常见的情况,比如我们异步访问服务端,获取数据A,然后有根据数据A再去访问服务端获取数据B,下面用setTimeout
来模拟异步。
回调函数返回一个未定状态(pending)的Promise的情况
function timeout(ms, data) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, data)
})
}
timeout(1000, 'lala').then((value) => {
console.log(value) //1000ms后打印lala
return timeout(2000, 'haha' + value)
}).then(console.log) //3000ms后打印hahalala
第一个then()
返回一个状态未定的Promise对象,该Promise对象状态2000ms后变为成功状态,所以2000ms后调用第二个then()
的对应的回调函数,而且回调函数的参数是由第一个Promise对象的resolve()
传过来的。除了这种后一个操作只依赖一个异步请求的情况,我们还会遇到后一步操作依赖多个异步请求的情况,而以来多个异步请求的情况又分两种:
- 后一步操作依赖所有异步请求的结果,即必须等到所有异步请求(为简便我们假设这些请求彼此互不依赖,即都是并行的)返回结果后才能执行后一步操作
- 后一步操作只依赖任意一个异步请求的结果,即只需某一个异步请求(为简便我们假设这些请求彼此互不依赖,即都是并行的)返回结果后就能执行后一步操作
对于第一种情况,Promise对象为我们提供了.all()
方法
all
Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例:
var p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise
实例,如果不是,就会先调Promise.resolve
方法,将参数转为 Promise
实例,再进一步处理。(Promise.all
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
p
的状态由p1
、p2
、p3
决定,只有当p1
、p2
、p3
的状态都为成功的时候,p
的状态才为成功,此时p1
、p2
、p3
的返回值组成一个数组,传递给p的回调函数;否则,只有要任何一个的状态为失败,那p
的状态就变成失败,此时第一个失败的实例的返回值,会传递给p的回调函数。下面看两个例子:
function timeout(ms, data) {
return new Promise((resolve, reject) => {
console.log(new Date())
setTimeout(resolve, ms, data)
})
}
function timeout1(ms, data) {
console.log(new Date())
return new Promise((resolve, reject) => {
setTimeout(reject, ms, data)
})
}
//成功情形:
Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))
/*打印
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:23:57 GMT+0800 (中国标准时间)
["kakaka", "lalala", "hahaha"] Wed Nov 01 2017 18:24:00 GMT+0800 (中国标准时间)
*/
//失败情形:
Promise.all([timeout(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))
/*
打印
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:27:59 GMT+0800 (中国标准时间)
Error: lalala
at Promise.all.catch.data (<anonymous>:1:117)
at <anonymous> Wed Nov 01 2017 18:28:01 GMT+0800 (中国标准时间)
*/
第一个为什么是3s不是6s,第二个为什么是2s不是3s,不是单线程吗?好吧,应该是setTimeout
的机制没搞清楚。下面我们来看race方法:
race
与.all()
不同的是,.race()
接受的数组里的Promise对象只有要任何一个改变状态,不管成功还是失败,就会执行对应的回调函数:
//成功情形:
Promise.all([timeout(1000, 'kakaka'), timeout(2000, 'lalala'), timeout(3000, 'hahaha')]).then(data => console.log(data, new Date()))
/*
打印:
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:34:41 GMT+0800 (中国标准时间)
kakaka Wed Nov 01 2017 18:34:42 GMT+0800 (中国标准时间)
*/
//失败情形:
Promise.all([timeout1(1000, 'kakaka'), timeout1(2000, 'lalala'), timeout(3000, 'hahaha')]).catch(data => console.log(new Error(data), new Date()))
/*
打印:
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Wed Nov 01 2017 18:36:00 GMT+0800 (中国标准时间)
Error: kakaka
at Promise.all.catch.data (<anonymous>:1:118)
at <anonymous> Wed Nov 01 2017 18:36:01 GMT+0800 (中国标准时间)
*/
好了,这次就先到这,后面还要再看看下Promise.resolve()
和Promise.reject()
还需要了解原生ajax对这两种情况的处理方式。
参考:
http://es6.ruanyifeng.com/#docs/promise
http://www.cnblogs.com/lvdabao/p/es6-promise-1.html