异步请求的处理方式
回调函数
所谓的回调函数就是函数作为参数的传递,在一个函数内部调用另一个函数,调用的同时可以把内部函数的数据传递出来,他的使用场景就是异步操作,数据需要等待一段时间才能返回的情况下可以使用回调函数
function foo(url, successCallback, failtureCallback) {
// 假设这是网络请求,需要请求完毕才能返回给用户使用
setTimeout(() => {
if (url === 'success') {
// 假设successCallback callback === 'function'
successCallback && successCallback({ code: '200', message: '请求成功' })
} else {
//failtureCallback callback === 'function'
failtureCallback && failtureCallback({ code: '-200', message: '请求失败' })
}
}, 2000)
}
foo('failture',
res => console.log(res),
err => console.log(err)
)
回调函数处理异步的弊端
- 如果是我们自己封装的函数,那么我们在封装的时候必须要自己设计好callback名称, 并且使用好,否则容易造成不理解。
- 如果我们使用的是别人封装的函数或者一些第三方库, 那么我们必须去看别人的源码或者文档, 才知道它这个函数需要怎么去获取到结果。
- 异步嵌套太多,容易造成回调地狱,代码看起来很不容易理解,可维护可阅读性都不高,找bug要花很多时间。
-
function foo(url, successCallback, failtureCallback) { // 假设这是网络请求,需要请求完毕才能返回给用户使用 setTimeout(() => { if (url === 'success') { successCallback && successCallback({ code: '200', message: '请求成功' }) } else { failtureCallback && failtureCallback({ code: '-200', message: '请求失败' }) } }, 1000) } foo('success', // 成功回调 (res) => { console.log(`第一次数据请求成功,接着请求第二次数据${res.code}`) foo('success', (res) => { console.log(`第二次数据请求成功,接着请求第三次数据${res.code}`) foo('success', (res) => { console.log(`第三次数据请求成功,接着请求第四次数据${res.code}`) foo('success', (res) => { console.log(`第四次数据请求成功,接着请求第五次数据${res.code}`) foo('success', (res) => { console.log(`第五次数据请求成功,接着请求第六次数据${res.code}`) foo('failture', (res) => { console.log(`第六次数据请求成功,接着请求第七次数据${res.code}`) }, (err) => { console.log(`第六次数据请求失败,gg了${err.code}`) }) }, (err) => { console.log(err) }) }, (err) => { console.log(err) }) }, (err) => { console.log(err) }) }, (err) => { console.log(err) }) }, // 失败回调 (err) => { console.log(err) })
上面代码就是典型的回调地狱,试问,如果你在真实项目开发中遇见这样的代码,你能够分析出问题的所在吗?所以这不是一种合理解决异步的方案。
Promise
Promise是一个类,实现异步编程的方法。
Promise的状态一旦被确定下来,无法被更改,resolve、reject两个函数不会代码禁止向下执行,为了防止继续向下执行,要加上return。
Promise的三个状态
- 待定( pending ) : 初始状态,既没有被兑现,也没有被拒
- 已兑现( fulfilled ) : 意味着操作已经完成 resolve
- 已拒绝 (rejected):意味着操作失败 reject
resolve不同值的区别
- 如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数
const promise = new Promise((resolve, reject) => { resolve("普通值") });
promise.then(res => {
console.log(res)
}, err => { })
- 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态
const promise = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
reject("我是新的promies, 我要改变之前promise的状态,这里会执行err")
}))
});
promise.then(res => {
console.log(res)
}, err => {
console.log(err)
})
- 如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据 then方法的结果来决定Promise的状态
const promise = new Promise((resolve, reject) => { const obj = { name: 'obj', then(resolve, reject) { reject('传入一个对象,对象里面有then方法,会改变之前promise的状态') } } resolve(obj) }); promise.then(res => { console.log(res) }, err => { console.log(err) })
then 方法的参数
// then方法接受两个参数 new Promise((resolve, reject) => { reject('嘿嘿') }).then( res => { console.log('resolve触发的回调函数'); }, err => { console.log('reject触发的回调函数'); } ) // 等价于 new Promise((resolve, reject) => { reject('嘿嘿') }) .then(res => { console.log('resolve触发的回调函数') }) .catch(err => { console.log('reject触发的回调函数') })
then 方法多次调用
const promise = new Promise((resolve, reject) => { reject('嘿嘿嘿') }) promise.then(res => { console.log(res) }).catch(err => { console.log(err) }) promise.then(res => { console.log(res) }).catch(err => { console.log(err) })
then/catch 方法返回值
then/catch方法本身返回的就是一个Promise,所以我们可以进行链式调用。
-
// catch打印完后又返回了一个promise 所以后面的then又会打印 new Promise((resolve, reject) => { reject('嘿嘿') }) .then(res => { console.log('resolve触发的回调函数'); }) .catch(err => { console.log('reject触发的回调函数'); return 123 }) .then(res => { console.log(res) })
finally 方法
finally是在ES9(ES2018)中新增的一个特性:表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码
new Promise((resolve, reject) => { reject('reject') }) .then(res => { console.log(res) }) .catch(err => { console.log(err) }) .finally(() => { console.log('我是最后被执行的') })
all 方法
它的作用是将多个Promise包裹在一起形成一个新的Promise。新的Promise状态由包裹的所有Promise共同决定。
// 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值 组成一个数组; const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('1') }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('2') }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('3') }, 3000) }) Promise.all([p3, p2, p1]).then(res => { console.log(res) }).catch(err => { console.log(err) })
//当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数; const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('1') }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('2') }, 2000) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { reject('3') }, 3000) }) Promise.all([p3, p2, p1]).then(res => { console.log(res) }).catch(err => { console.log(err) })
allSettled 方法
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
该方法会在所有的Promise都有结果(settled),无论是fulfilled,还是reject时,才会有最终的状态。
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve(11111) }, 1000); }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject(22222) }, 2000); }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve(33333) }, 3000); }) // allSettled Promise.allSettled([p1, p2, p3]).then(res => { console.log(res) }).catch(err => { console.log(err)
race 方法
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法。
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('1') }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('2') }, 998) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('3') }, 1002) }) // 不管是resolve 还是reject 谁先有结果,那么就使用谁的结果 Promise.race([p1, p2, p3]) .then(res => { console.log(res) }) .catch(err => { console.log(err) })
any 方法
和race方法是类似的,any方法会等到一个fulfilled状态,才会决定新Promise的状态,如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
const p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('1') }, 1000) }) const p2 = new Promise((resolve, reject) => { setTimeout(() => { reject('2') }, 998) }) const p3 = new Promise((resolve, reject) => { setTimeout(() => { resolve('3') }, 1002) }) // 会打印p1 Promise.any([p1, p2, p3]) .then(res => { console.log(res) }) .catch(err => { console.log(err) })