Promise是异步编程的一种解决方案,比传统的解决方案-回调函数和事件-更合理更强大。简单讲,Promise里储存着某个将来才会结束的事件的结果,从它能够获取异步操做的消息。 Promise对象有如下两个特色:javascript
Promise能够方便的将异步操做以同步操做的流程表达出来,避免层层嵌套的回调函数。java
先建立一个Promise实例:编程
const promise = new Promise((resolve, reject) { // ...some code if(/*异步操做完成*/) { resolve(value) else { reject(error) } }) 复制代码
Promise构造函数接收一个函数做为参数,该函数的两个参数分别是resolve函数和reject函数。resolve函数的做用是将Promise的状态从pending变成resolved,在异步操做成功时调用,并将结果做为参数传递出去;reject函数的做用时将Promsie的状态从pending变成rejected,在异步操做失败时调用,并将异步操做的错误做为参数传递出去。json
Promise的then方法能够分别指定Resolved状态和rejected状态的回调函数。其中第一个回调做为Resolved状态的回调,第二个回调做为rejected状态的回调,而第二个回调是可选的。数组
下面咱们来猜一猜下面的代码的打印顺序是什么样的:promise
let promise = new Promise((resolve, reject)=>{ console.log('Promise'); resolve() }) promise.then(() => { console.log('Resolved'); }) console.log('Hi') 复制代码
让咱们先来推理一下:首先,Promise构造函数中的代码会在新建的时候当即执行,因此,第一个打印的应该是“Promise”;其次then方法指定的回调函数会在当前脚本全部同步任务执行完以后才执行,因此“Resolved”应该是最后打印的。总上所述。打印顺序应该是bash
// Promise // Hi // Resolved 复制代码
还有一点须要注意:resolve和reject并不会阻止后面代码的执行,而且后面的代码还会先执行markdown
new Promise((resolve, reject) => { resolve(1); console.log(2); }).then((res) => { console.log(res) }) // 2 // 1 复制代码
这是由于执行resolved是在本次事件循环的末尾执行,老是晚于本次循环的同步任务。 通常来讲,不该该在resolve或reject以后写代码,应该将其放在then方法里面。为了预防万一,咱们能够在resolve和reject前加上return语句。异步
请看下面的例子,来猜一猜最后会进入then仍是catch呢:异步编程
const p1 = new Promise((resolve, reject) => { setTimeout(() => reject(new Error('fail')), 3000) }) const p2 = new Promise((resolve, reject) => { setTimeout(()=> resolve(p1), 1000) }) p2.then((res)=> {console.log(res)}).catch((err) => {console.log(err)}); // Error:fail 复制代码
答案是会进入catch。这是由于p1的状态会传递给p2,也就是说p1的状态决定了p2的状态,若是p1是pending那么p2的回调会等待p1的状态改变;若是p1的状态是resolved或rejected,那么p2的回调会当即执行。因此上面的代码会在3秒后触发catch指定的回调。
then方法的做用是为Promise实例添加状态改变时的回调函数。它的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
注意:then方法返回的是一个新的Promise实例(注意不是原来的Promise实例)。所以能够采用链式写法,即.then方法后面再调另外一个.then。而且上一个then方法返回的结果会做为回调方法的参数。
采用链式的then方法能够依次指定一组按照顺序调用的回调函数,而且前一个回调函数可能返回的仍是一个Promise实例,然后面的回调函数则会等待该Promise对象状态发生改变时再被调用。看下面代码你就懂了:
getJSON('/posts.json') .then(post => getJSON(post.commentURL)) .then( comments => {console.log('resolved', comments)}, err => {console.log('rejected', err)} ) 复制代码
上面的代码getJSON返回的时一个Promise对象。因此第一个then返回的就是一个Promise,此时第二个then方法指定的回调函数就会等待这个新的Promise对象状态发生改变时,resolved就调用第一个回调,rejected就会调用第二个回调。
catch方法是用于指定发生错误时的回调函数。注意它不止能够捕获Promise抛出的错误还能捕获前一个回调函数运行时发生的错误。
getJSON('/posts.json').then(res => { throw new Error('error') }).catch(err => { console.log(err) }) // Error error 复制代码
上面的代码中,getJSON这个Promise对象的状态若为resolved,会进入then方法,若为rejected则会进入catch方法。另外,若是then方法指定的回调在运行中抛出错误也会被catch捕捉。
Promise对象的错误具备'冒泡'的性质,会一直向后传递,直到被捕获,也就是说错误老是会被下一个catch语句捕获。
getJSON('/posts.json').then(post => { return getJSON(post.commentURL) }) .then(comments => { // somecode }).catch(err => { // 处理前面三个Promise产生的错误 }) 复制代码
咱们通常使用catch捕获错误,而不使用then方法的第二个参数。
须要注意的是,catch方法返回的仍是一个Promise对象,所以后面还能接着调用then方法。
若没有指定catch方法处理错误,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。
Promise.all方法用于将多个Promise实例包装成一个新的Promise实例。Promise.all方法接收的参数必须具备Iterator接口且返回的每一个成员都必须是Promise实例。若不是Promise实例,Promise.all方法内部就会先执行Promise.resolve方法将参数转为Promise实例。
let p = Promise.all([p1,p2,p3]) 复制代码
上面代码中,p的状态由p1,p2,p3决定,分为两种状况:
一、只有p一、p二、p3的状态都是Fulfilled,p的状态才会是Fulfilled,此时,p一、p二、p3的返回值组成一个数组,传递给p的回调函数。
二、只要p一、p二、p3任一个状态为rejected,则p的状态为rejected,返回第一个被rejected的实例的返回值给p的回调函数。
看下面的例子:
const promises = [1,2,3].map(id => { return getJSON('/post' + id + '.json') }); Promise.all(promises).then((res) => { // ... }).catch(err => { // ... }) 复制代码
若是做为参数的Promise实例自身定义了catch方法,那么它被rejected时并不会触发Promise.all的catch方法。 看下面例子:
const p1 = new Promise((resolve,reject) => { resolve('hello') }).then((res) => res) .catch(e => e); const p2 = new Promise((resolve, reject) => { throw new Error('error!') }).then(res => res) .catch(err => err); Promise.all([p1,p2]) .then(res => console.log(res)) .catch(err => {console.log(err)}) // ["hello", Error: error!] 复制代码
上面的代码中p2会rejected,可是Promise.all()会进入then的回调,这是由于p2有本身的catch方法,而该方法返回的是一个新的Promise实例,p2实际上指向的是这个实例,而这个实例执行完以后也会变成resolved,因此all方法中的两个实例都是resolved,所以不会调用catch方法。
race方法一样是将多个Promise实例包装成一个新的实例。
let p = Promise.race([p1,p2,p3]) 复制代码
不一样之处是race方法的状态在第一个参数实例率先改变状态后,p的状态就会跟着改变,并将那个率先改变的实例的返回值传递给p的回调。同all方法同样,若参数中有不是Promise实例的,会先调用resolve方法,将参数转换成Promise实例再进一步处理。
用下面的例子介绍下race的使用场景:
const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise((resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]) p.then(res => console.log(res)); p.catch(err => console.log(err)); 复制代码
上面的代码中,若是5秒内fetch方法无返回结果,则p的catch回调就会被触发。
有时候须要将现有的对象转换成Promise.resolve对象,这时就要用到Promise.resolve方法。
Promise.resolve('foo'); // 等价于 new Promise(resolve => resolve('foo')) 复制代码
Promise.resolve方法的参数分红如下四种状况: 一、Promise实例
若是参数时Promise实例,那么Promise.resolve将不做任何修改,直接返回这个实例。
二、thanable对象
thanable对象是指具备then方法的对象,好比下面这个:
let thenable = { then: (resolve, reject) => { resolve(42) } } 复制代码
Promise.resolve方法会将这个对象转换成Promise对象,并当即执行thenable对象的then方法。
三、不具备then方法的对象或者根本不是对象
若是参数是一个原始值或者不具备then方法的对象,那么Promise.resolve返回的是一个新的Promise对象,状态为rejected,Promise.resolve方法的参数会同时传给回调函数,而且回调会当即执行。
四、不带任何参数 不带任何参数的话,resolve会直接返回一个带有resolved状态的Promise对象,须要注意的是,当即resolved的Promise对象是在本轮事件循环结束时执行,而不是在下一轮事件循环开始时执行。
Promise.reject方法也会返回一个新的Promise实例,状态为Rejected。与Promise.resolve方法不一样的一点是,reject方法的参数会原封不动的做为reject的理由变成后续方法的参数。以下:
const thenable = { then: (resolve, reject) => { reject('error') } } Promise.reject(thenable).catch(e => { console.log(e === thenable) }) // true 复制代码
上面代码中,Promise.reject方法的参数是一个thenable对象,然后面catch方法的参数不像resolve方法同样,抛出的‘error’字符串,而是这个thenable对象自己。
这篇文章是对阮一峰老师的《ES6标准入门》的一个学习笔记吧,内容大可能是来源于书中,在此整理一下方便后续复习巩固。对Promise有其余想法的同窗欢迎评论指教哦。
阮一峰 《ES6标准入门》