先使用已经造好的promise
听过两个不同的讲promise的视频,感觉先使用promise再学习如何创造会理解更好。mdn上也是这个顺序。而且实际开发中,我们可能也是用别人造好的promise更多。
以fetch做例子,fetch函数会返回一个promise,所以我们可以:
let promise = fetch('https://catfact.ninja/fact')
// 这里理论上可以写 (white promise is not resolved){wait.......} 但这样会造成阻塞
// 处理promise成功和失败的情况
promise.then(resp => console.log(res)) // then的return对象是一个新promise,如果then未处理,promise里的信息会被传递给catch
.catch(err => console.log(err))
根据官方文档,then可以有以上两种穿参形式,我们可以只传递成功的cb,出错时的cb交给catch,也可以把两个cb都传给then
也就是说,下面这两种写法是等价的。
promise.then(resp => console.log(resp))
.catch(err => console.log(err))
promise.then(resp => console.log(resp), err => console.log(err))
下面来看一下输出结果。 上面api获取的数据其实应该是下面这样,
但实际打印出来是这样
为了拿到我们想要的数据,可以使用json()方法,但是json方法返回的还是一个promise,所以我们需要再次用then来拿数据。
promise.then(resp => resp.json().then(data => console.log(data)))
.catch(err => console.log(err))
上面的例子promise我们只使用了一次,可以省去变量命名,直接写:
fetch('https://catfact.ninja/fact').then(resp => {}, err=> {})
make your own promise
假设我们直接用new构建一个promise,然后调用then,看看会发生什么
delay().then(console.log('delay'))
function delay() {
return new Promise();
}
这时浏览器会报错
也就是说没有promise resolver,而且这个promise resolver需要是函数。 可以理解为promise像是一个合约,合约达成了做什么? 违反合约做什么?这些都是需要提前规定好并写入合约,这样这份合约才能生效。
要创建promise,我们必须要定义一个用来处理promise状态改变后做什么的函数,官方文档称它为executor(执行器)
new Promise(executor)
而executor的样子是:
function(resolutionFunc, rejectionFunc){
// 通常是一些异步操作
}
可以用箭头函数直接简写为:
function delay() {
return new Promise((resolve, reject) => { });
}
接着把代码改造成这样
delay().then(resp => sayHello())
function sayHello() {
console.log('hello')
}
function delay() {
return new Promise((resolve, reject) => { resolve() });
}
// 这个resolve实际上是then成功后做的事,reject则是catch error后做的事
console会输出“hello”
promise并不是说拿到resolve和reject方案后,在fulfill时直接调用resolve,在发生错误时直接调用reject,而是可以让你再做一些别的事,比如在1秒后再调用resolve。下面补充了一些代码,加上了错误处理
delay('string').then(resp => sayHello())
function delay(time) {
return new Promise((resolve, reject) => {
if (isNaN(time)) {
reject(new Error('time is not a valid number'))
} // 错误判定必须放在前面,不然resolve一定会执行。
setTimeout(resolve(), time)
});
}
代码执行情况
await async
这块我需要整理的思路比较少,把上面的fetch相关那部分改造一下。
let url = 'https://catfact.ninja/fact'
getCatFact()
.then(result => console.log(result))
.catch(err => console.log(err))
async function getCatFact() {
let result = await fetch(url)
let json = await result.json()
return json
}
Promise.all() & Promise.allSettled
当我们有好几个promise需要处理时,直接把它们每个都书写出来,执行顺序未必是我们想要的。
想要保证执行顺序的一种方法是把他们chain起来。
// 理论上下面这种写法,是不能保证执行顺序一定是书写顺序的
getCatFact().then(result => {
console.log('result 1');
})
getCatFact().then(result => {
console.log('result 2');
})
getCatFact().then(result => {
console.log('result 3');
})
// 把它们chain起来能保证顺序
getCatFact()
.then(result => {
console.log(result)
return getCatFact()
})
.then(result => {
console.log(result)
return getCatFact()
})
.then(result => {
console.log(result)
})
我们还可以用Promise.all()来解决问题。它的basic idea是:
let promises = [___, ____, ____];
Promise.all(promises)
.then(result => {
// do something
// result会是一个array
})
.catch(err => console.log(err))
前面的chain可以替换成下面:
let promises = [getCatFact(), getCatFact(), getCatFact()]
Promise.all(promises).
then(result => console.log(result))
现在,假设我们故意制造一个error
let url1 = 'https://catfact.ninja/fact'
let url2 = 'https://catfact.ninjamiss/fact' // 这里url有错误
let url3 = 'https://catfact.ninja/fact'
let promises = [getCatFact(url1), getCatFact(url2), getCatFact(url3)]
Promise.all(promises)
.then(result => console.log(result))
async function getCatFact(url) {
let result = await fetch(url)
let json = await result.json()
return json
}
这时控制台不会输出任何我们想要的cat fact,而是会直接报错。
这也反应出Promise.all的特性:all or nothing,要么全部输出,要么没有结果。
如果要避免这点,可以使用Promise.allSettled()
Promise.allSettled(promises)
.then(result => console.log(result))
此时它会以对象构成的数组形式输出数据
此时,如果我们想要拿到成功的数据,可以这样做:
Promise.allSettled(promises)
.then(result => {
result.forEach(r => {
if (r.status == "fulfilled") {
console.log(r.value)
}
})
})
尚硅谷课程笔记
为什么要用promise
- 支持链式调用,可以解决回调地狱的问题。
2. callback比較靈活,不需要在一开始就指定好,而可以在then的时候再进行指定。
基本流程
一些注意事项
- executor是会立刻执行的。
- Promise.resolve()
当参数是非 Promise类型时,Promise.resolve() 结果以这个参数为结果,并且状态为fulfilled
const p = Promise.resolve('hello')
p.then(r => console.log(r)) // hello
当参数是Promise类型时,返回的Promise状态和结果由参数中的Promise决定。
const fullfilledP = new Promise((resolve, reject) => {
resolve('success')
})
const p = Promise.resolve(fullfilledP)
console.log(p) // [[PromiseState]]: "fulfilled" , [[PromiseResult]]: "success"
const rejectedP = new Promise((resolve, reject) => {
reject('error')
})
const p2 = Promise.resolve(rejectedP)
console.log(p2) // [[PromiseState]]: "rejected", [[PromiseResult]]: "error"
- Promise.reject() 当参数为Promise时,不论参数Promise状态如何,返回的Promise都是rejected状态,它的result也会是参数Promise对象。
const fullfilledP = new Promise((resolve, reject) => {
resolve('success')
})
const p = Promise.reject(fullfilledP)
console.log(p) // [[PromiseState]]: "rejected" [[PromiseResult]]: Promise
const rejectedP = new Promise((resolve, reject) => {
reject('error')
})
const p2 = Promise.reject(rejectedP)
console.log(p2) // [[PromiseState]]: "rejected" [[PromiseResult]]: Promise
- 改变promise状态和指定回调函数谁先谁后? 根据情况都有可能。 当在执行器中直接调用resolve()/ reject() 或者 延迟更长时间才调用then时,状态改变会先发生。
5.then方法的返回结果是由什么决定的? 由指定的callback的返回结果决定的。
const promise = new Promise((resolve, reject) => {
resolve()
})
// 无return值
const thenReturnValue = promise.then(() => {})
console.log(thenReturnValue) // [[PromiseState]]: "fulfilled" // [[PromiseResult]]: undefined
// return 普通值
const thenReturnValue2 = promise.then(() => {
return 123
})
console.log(thenReturnValue2) //[[PromiseState]]: "fulfilled" [[PromiseResult]]: 123
// return Promise
const thenReturnValue3 = promise.then(() => {
return Promise.reject('error')
})
console.log(thenReturnValue3) // [[PromiseState]]: "rejected" [[PromiseResult]]: "error"
// throw exception
const thenReturnValue4 = promise.then(() => {
throw 'error'
})
console.log(thenReturnValue4) // [[PromiseState]]: "rejected" [[PromiseResult]]: "error"
// 当promise被reject,但是handler返回一个成功promise时的测试
const p = new Promise((r1, r2) => {
r2('error')
})
console.log(p.then(() => {}, () => { //[[PromiseState]]: "fulfilled" [[PromiseResult]]: "success"
return Promise.resolve('success')
}))
- 链式then调用的结果是由前面的返回值决定的 例如:
let p = new Promise((resolve, reject) => {
resolve(123)
})
p.then(res => {
return new Promise((resolve, reject) => {
resolve('success')
})
}).then(res => {
return 456
}).then(res => console.log(res)) //456
- 异常穿透问题 在链式调用时任何环节的错误只需在最后用catch捕获。
let p = new Promise((resolve, reject) => {
resolve('success')
})
p.then((res) => {
return new Promise((resolve, reject) => {
reject('error')
})
}).then(res => {
return Promise.resolve(0)
}).catch(err => console.log(err)) //error
- 中断promise链 例如假设我们想在console.log(1)之后就中断,该如何做?
let p = new Promise((resolve, reject) => {
resolve('hello')
})
p.then(res => {
console.log(1) // 执行这里后中断
}).then(res => {
console.log(2)
}).then(res => {
console.log(3)
})
假设我们按照以前中断一个函数的方法直接return,试一试,先看一下直接return后then的返回结果是什么。
let result = p.then(res => {
console.log(1)
return
})
console.log(result) // [[PromiseState]]: "fulfilled" , [[PromiseResult]]: undefined
也就是说直接return后,then会返回一个状态是resolved、结果是undefined的promise,因为结果是resolved,那么必然之后的then还会执行。
同样,return null、false等也是一样的结果。
假设这里直接抛出异常,确实可以起到中断效果,但也会报错。
这里最好的处理方法只有一个,就是return一个pengding状态的promise,因为then函数只有在promise状态改变由pending发生改变时,才会被调用。
p.then(res => {
console.log(1)
return new Promise(() => {})
}).then(res => {
console.log(2)
}).then(res => {
console.log(3)
})