一、发展
直接使用传统回调方式去完成复杂的异步流程,无法避免大量的函数嵌套,就会导致函数回调地狱问题,为了避免回调地狱问题,CommonJS社区提出了promise规范,目的就是为异步编程一共一种更合理更强大的解决方案,后来在ES2015中被标准化,成为语言规范,也就是ES6中的promise对象。
二、概念
Promise对象用于异步操作,表示一个尚未完成且预计在未来完成的异步操作。异步过后会返回成功或者失败两个状态。
promise中的resolve就是将pending变成fulfilled(未完成变成成功),reject就是将状态从pending变成rejected(未完成变成失败)。一旦状态改变,就不会再变,任何时候都可以得到这个结果,最后都会有相应的操作。
三、Promise的三个状态
1、pending:待定,初始状态,不是成功或失败状态。
2、fulfilled:完成,意味着操作成功完成。
3、rejected:被否决,意味着操作失败。
四、Promise 优缺点
优点:可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
缺点:无法取消 Promise,一旦新建它就会立即执行,无法中途取消。如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部,当处于 Pending 状态时,无法得知目前进展到哪一个阶段。
五、Promise的基本用法
const promise = new Promise (function (resolve, reject) {
// 只能调用其一
resolve(100) // 承诺成功
reject(new Error('promise rejected')) // 承诺失败
})
// then中第一个函数就是成功后的回调函数,第二个就是失败后的回调函数
promise.then(function (params) {
console.log('resolved', params)
}, function (err) {
console.log('rejected', err)
})
//如果promise中没用任何异步操作,then中任务任进入到queue中进行排队,必须要等待同步代码全部执行完了,才会执行then中的任务
六、promise案例
function ajax (url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.responseType = 'json'
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
ajax('/api.json').then(res => {
console.log(res, 'res')
}, err => {
console.log(err, 'err')
})
七、Promise链式调用
ajax('/api.json')
.then(res => {
console.log(res, 'res')
}) // => promise
.then(res => {
console.log(res, 'res1')
})
.then(res => {
console.log(res, 'res2')
})
.then(res => {
console.log(res, 'res3')
})
.then(res => {
console.log(res, 'res4')
})
每一个then方法都是在为上一个then返回的promise对象添加状态后的回调,这些promise会依次执行。then中如果没有返回值默认的就是undefined
Promise对象的then方法会返回一个全新的Promise对象
后面的then方法就是在为一个then返回的Promise注册回调
前面then方法中回调函数的返回值作为后面then方法回调的参数
如果回调中返回的是Promise,那后面then方法的回调会等待它的结束
八、异常处理
1.onRejected回调就是为promise中的异常做出处理,在promise中调用不存在的方法或者手动抛出一个错误(throw new Error())等异常或者promise失败了都会触发onRejected回调。
2.可以用then方法注册成功的回调,用catch方法注册失败onrejected回调,也可以用then中的第一个参注册成功回调,第二参注册失败回调。
3.catch方法也类试于then方法第一参传递了一个undefined,第二参失败回调。
4.catch方法和then方法第二参直接注册失败回调的差异
每个then方法返回的都是一个全新的Promise对象,也就是说我门通过链式调用的方式调用catch实际上是在给前面then方法返回的全新Promise去指定失败的回调,并不是直接给第一次promise对象所指定的,因为这是同一个promise链条,那前面promise上的异常会一直往后传递,所以catch才可以捕获到第一个promise上的异常。
而通过then方法的第二个参数去指定的失败回调函数,那它只是给第一个promise指定的,也就是它只能捕获到这个promise上的异常。
在表象上的差异,如果在then方法当中返回了第二个promise,而且这个promise执行过程当中出现了异常,如果在第一个promise中then的第二个参数上注册的失败回调,是捕获不到第二个promise上的异常,因为它只是在第一个上面注册的失败回调。使用catch的话就可以捕获到,因为这样失败回调就是绑定在then返回的promise上面。
在链式的调用上去分开指定成功和失败的回调,因为promise链条上任何一个异常都会向后传递,直到被捕获
5.unhandledrejection
unhandledrejection去处理一些我们代码当中没有被手动捕获的promise异常。
在浏览器当中把这个事件注册在window对象上
window.addEventListener('unhandledrejection', event => {
const { reason, promise} = event
// reason => Promise 失败原因,一般是一个错误对象
// promise => 出现异常的promise对象
event.preventDefault()
}, false)
在node当中需要在process.on对象上去注册
九、promise的静态方法
1.Promise.resolve():快速的把一个值转换为promise对象
Promise.resolve('foo').then(res => {
console.log(res) // foo
})
// foo就会做为这个promise对象返回的值
// 与下面例子是等价的
new Promise((resolve, reject) => {
resolve('foo')
})
如果 Promise.resolve 方法的参数,不是具有 then 方法的对象(又称 thenable 对象),则返回一个新的 Promise 对象,且它的状态为fulfilled。
如果Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。
如果resolve参数是一个key为then的对象,这样一个值也可以做为promise对象被执行
Promise.resolve({
then: function (onFulfilled, onRejected) {
onFulfilled('foo')
}
}).then(value => {
console.log('value') // foo
})
2.Promise.reject():快速创建一个是失败的promise对象
Promise.reject(new Error()).catch(err => {
console.log(err)
})
// reject的参数就是回调函数返回的值
3.Promise.all()方法
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例统一去管理,用于多个promise并行执行时统一管理。
promise的all方法接受的是一个数组,数组中的元素都是Promise对象的实例(Promise.all 方法的参数不一定是数组,但是必须具有 iterator 接口,且返回的每个成员都是 Promise 实例。)
all方法会返回一个全新的promise对象,当内部所以的promise都完成之后返回的这个全新promise才会完成,那么这个promise拿到的结果就是一个数组,包含的每个promise执行完的结果,当其中一个失败了,这个新的promise就会以失败结束。
var p = Promise.all([p1,p2,p3]);
4.Promise.race()方法
race方法同样可以讲多个Promise对象组合成一个全新的promise对象
all方法会等待所有任务结束,race只会等待第一个结束的任务
十、promise执行顺序
回调队列中的任务称之为宏任务,宏任务执行过程中可以临时加上一些额外的需求,这些需求可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的微任务
微任务直接在当前任务结束过后立即执行,而不是到整个队伍的末尾重新排队。是为了提高整体的响应能力
promise的回调会作为微任务执行的,所以它会再本轮调用的末尾去执行
settimeout以宏任务的形式进入到队列的末尾
目前绝大多数异步调用都是作为红任务执行,而Promise和MutationObserver,node中的process.nextTick会作为微任务在本轮调用的末尾执行。