Promise
Promise是ES6新增的引用类型,通过new来实例化时需要传入执行器函数(executor)作为参数,这个执行器函数内部可以封装异步操作。
Promise类的实例对象是有**状态(state)**的,对应三个值:pending(待定)、fullfilled(成功)、rejected(失败)
(实例的初始状态为pending,一旦由pending状态落定成fulfilled状态或rejected状态,就不可再次改变,该过程是不可逆的)
与之对应,Promise类实例对象有成功的结果(value)和失败的原因(reason),默认值都为undefined,只有状态被改变后(从初始默认状态落定成功或失败的状态)才会为其赋值。
注意:Promsie内部的state、value、reason是私有的,外界是访问不到的
//向里面传入执行器函数,执行器函数默认两个参数对应改变状态的两个函数
let promise=new Promise((resolve,reject)=>{
//...一系列操作...
//改变实例的状态:pedding->fulfilled,成功的结果value为“我是成功的结果”
resolve("我是成功的结果")
})
console.log(promise)
//向里面传入执行器函数,执行器函数默认两个参数对应改变状态的两个函数
let promise=new Promise((resolve,reject)=>{
//...一系列操作...
//改变实例的状态:pedding->rejected,失败的原因reason为“我是失败的原因”
reject("我是失败的原因")
})
console.log(promise)
正如开头说的,在我们实例化一个Promise时会传入一个执行器函数来进行一系列的操作,这个执行器函数接受两个参数:resolve、reject他们都是回调函数,用来落定状态。在这个执行器函数里面就可以根据这一系列的操作的实际情况通过这两个回调来规定该实例对象的状态是fulfilled(成功),还是rejected(失败),并设置成功的结果或失败的原因。
注意:如果在执行器函数内部抛出错误也会是promsie实例的状态落定成rejected
关于resolve回调函数的参数
-
传入普通的值或对象,Promise实例的状态直接落定成fulfilled(正如上面的示例)
-
传入一个新的Promise实例,当前的Promise实例的状态由这个新的Promise来决定,转态转移。
let promise = new Promise((resolve, reject) => { //...一系列操作... //状态转移为新的Promise resolve(new Promise((res, rej) => { rej('cuole') })) }) console.log(promise)
-
传入一个对象,并且这个对象内部实现了then方法,那么会自动执行这个then方法同时会将resolve和rejected回调传到then方法中,又这个then方法来决定最终的转态
let promise = new Promise((resolve, reject) => { //...一系列操作... const obj = { then() { console.log(233) return 123 } } resolve(obj) })
let promise = new Promise((resolve, reject) => { //...一系列操作... const obj = { //这里会自动将res和rej回调传递进来 then(res, rej) { console.log(233) //最终转态落定成功,成功的结果为233 res('233') } } //改变实例的状态:pedding->fulfilled,成功的结果value为“我是成功的结果” resolve(obj) }) console.log(promise)
上面的后两种特殊情况也可能交替出现:
let promise = new Promise((resolve, reject) => {
//...一系列操作...
const obj = {
then(res, rej) {
console.log(233)
res('467987987416455')
}
}
resolve(obj)
})
let promise2 = new Promise((res, rej) => {
//这里执行的res参数是一个Promsie实例,并且这个实例里面执行resolve时传入的是一个实现了then方法的对象
res(promise)
})
console.log(promise2)
let promise2 = new Promise((res, rej) => {
res('564659879')
})
let promise = new Promise((resolve, reject) => {
//...一系列操作...
const obj = {
then(res, rej) {
console.log(233)
//这里then方法的res执行的参数是一个Promsie实例
res(promise2)
}
}
//
resolve(obj)
})
console.log(promise)
上面说到Promise内部的值是私有的,那么外部代码需要访问这个成功或失败的值呢?这时候就需要Promsie类为我们提供的方法了。
Promise.prototype.then()
- then()
在原型上的then方法最多接收两个参数,onResolved处理程序和onRejected处理程序,都是可选的。通过这两个参数我们可以拿到实例成功或失败的结果。同时then方法也会返回一个新的Promise实例对象(下面一律称为promise2)。
注意:只有Promsie内部的状态落定了,then方法中对应的处理程序才会执行。
//向里面传入执行器函数,执行器函数默认两个参数对应改变状态的两个函数
let promise = new Promise((resolve, reject) => {
//改变实例的状态:pedding->fulfilled,成功的结果value为“我是成功的结果”
reject("我是失败的原因")
})
promise.then(undefined, reason => {
console.log(reason)
})
//打印 “我是失败的原因”
let promise = new Promise((resolve, reject) => {
//抛出一个错误
throw new Error('over')
})
let promise2 = promise.then(undefined, reason => {
console.log(reason)
})
//打印 Error: over
//......
- 链式调用
上面说到then方法返回一个新的Promise实例,那么就再次可以使用then方法,不断地.then().then(),以此类推:new Promsie((res,rej)=>(res(‘bar’))).then().then().then() … ,这就是then方法的强大之处,可以链式调用
举个例子:
//定义一个函数,里面有两个参数,str和回电函数
function fn(str, callback = null) {
//一秒后打印str,并执行回调函数
setTimeout(() => {
console.log(str)
if (callback) {
callback()
}
}, 1000)
}
fn('你', () => {
fn('好', () => {
fn('啊')
})
})
//打印结果:
// 你(1秒后)
// 好(2秒后)
// 啊(3秒后)
这里只有三次回调,真实的业务要比这复杂得多,那么回调会更多。如果没有更方便更直观的方法,那么就这样一直回调,回调,回调…
下面使用Promise实现的同样的功能:
function fn2(str) {
return new Promise((res, rej) => {
setTimeout(() => {
console.log(str)
res()
}, 1000)
})
}
fn2('你')
.then(() =>
fn2('好')
).then(() =>
fn2('啊')
)
//打印结果:
// 你(1秒后)
// 好(2秒后)
// 啊(3秒后)
这里面虽然没有通过链式调用来拿上一次的结果,但是就这个例子而言已经可以看出相比上面的写法通过promise更为直观。then里面的处理程序会等待前一个实例解决,然后实例化一个新的实例并返回,这样可以串行异步任务。
说到then返回一个新的promsie实例,那么then方法返回一个什么样的promise实例呢,里面是成功的还是失败的呢?下面总结了几个规则:
- then方法内没有传入相应的处理程序,或者原promise处于pending状态都会返回一个和原来相同的promsie2(虽然二者看起来一样,但是指针指向的不是同一个对象)
let A = new Promise((res, rej) => {
rej('123')
})
console.log(A.then())
console.log(A.then(()=>{},undefined))
console.log(A)
console.log(A===A.then())
let A = new Promise((res, rej) => {
setTimeout(() => {
rej('123')
}, 3000)
})
let promise2 = A.then(undefined, () => {
return "111"
})
//此时原本的A实例处于pending状态,处理程序没有执行,打印的then方法返回的promise2也处于pedding状态
console.log(promise2)
//定时四秒超时后A的状态早已落定,then执行了onResolved处理程序(为啥返回结果不一样呢,往下看)
setTimeout(() => {
console.log(promise2)
}, 4000)
-
对应的位置传入了处理程序(看返回值):
(1)如果对应的处理程序返回值不是一个promsie对象,那么返回的promise2对象执行了resolve(处理程序的返回值)let A = new Promise((res, rej) => { res('123') }) let promise2 = A.then(value => { console.log('我拿到了成功的结果' + value) return "我是promise2的结果" }) console.log(promise2)
let A = new Promise((res, rej) => { rej('123') }) let promise2 = A.then(undefined, reason => { console.log('我拿到了失败的原因' + reason) return "我是promise2的结果" }) console.log(promise2)
(2)如果对应的处理程序的返回值是一个promsie对象/一个实现了then方法的普通对象,那么返回的promise2的转态由这个promise对象/普通对象的then方法来决定
let A = new Promise((res, rej) => { rej('123') }) let promise2 = A.then(undefined, () => { return new Promise((res, rej) => { res('111') }) }) console.log(promise2)
let A = new Promise((res, rej) => {
rej('123')
})
let promise2 = A.then(undefined, () => {
return new Promise((res, rej) => {
//无结果
})
})
console.log(promise2)
let A = new Promise((res, rej) => {
rej('123')
})
let promise2 = A.then(undefined, () => {
return new Promise((res, rej) => {
rej("111")
})
})
console.log(promise2)
let A = new Promise((res, rej) => {
rej('123')
})
let promise2 = A.then(undefined, () => {
return {
then(res, rej) {
//如果没有落定状态,那么最后的promsie2也是pending状态
rej('456')
}
}
})
console.log(promise2)
(3)执行对应处理程序出现异常,返回的promsie2执行了reject(e)
let A = new Promise((res, rej) => {
rej('123')
})
let promise2 = A.then(undefined, () => {
throw(new Error('出错啦'))
})
console.log(promise2)
Promise.protorype.catch
catch方法就是一个语法糖,只接收一个参数,onRejected处理程序。他的作用和调用Promise.prototype.then(null,onRejected)是一样的。
let promise1 = new Promise((res, rej) => {
//打印一个未定义的变量,这里会出错
console.log(a)
res()
})
//控制台是一样的结果
promise1.then(null, reason => console.log(reason))
promise1.catch(reason => console.log(reason))
思考一下如果这样的写法,结果会是什么样呢?
promise.then(null, reason => console.log(reason)).catch(reason => console.log(reason))
这个catch捕捉的还是promsie1的错误,他的实现原理是最先捕捉第一个promise也就是promsie1,如果没出错则向下(返回的新的promise)捕捉,以此类推。而不是我们想象的返回的新的promise来调用catch,这个cath就会取捕捉这个返回的新的promise出现的错误。
let promise1 = new Promise((res, rej) => {
res('success')
})
promise1.then(vaue => new Promise((res, rej) => {
rej('失败啦0.0')
}), null).catch(reason => console.log(reason))
//打印:
//失败啦0.0
这里很明显捕捉的是返回的promise实例而不是promsie1实例