异步请求的处理方式
回调函数
所谓的回调函数就是函数作为参数的传递,在一个函数内部调用另一个函数,调用的同时可以把内部函数的数据传递出来,他的使用场景就是异步操作,数据需要等待一段时间才能返回的情况下可以使用回调函数。
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是一个类,可以翻译成承诺、许诺、期约。
// resolve , reject 是两个回调函数
// 当我们调用resolve的时候 , 会执行Promise对象的then方法传入的回调函数
// 当我们调用reject的时候 , 会执行Promise对象的catch方法传入的回调函数
const promise = new Promise((resolve, reject) => { })
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) })
如果全部reject
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('1')
}, 1000)
})
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('2')
}, 998)
})
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('3')
}, 1002)
})
// 那么会报一个AggregateError的错误。
Promise.any([p1, p2, p3])
.then(res => { console.log(res) })
.catch(err => { console.log(err, err.errors) })
使用Promise重构回调函数(一)
function foo(url) {
return new Promise((resolve, reject) => {
// 假设这是网络请求,需要请求完毕才能返回给用户使用
setTimeout(() => {
if (url === 'success') {
resolve({ code: '200', message: '请求成功' })
} else {
reject({ code: '-200', message: '请求失败' })
}
}, 1000)
})
}
foo('success').then(res => {
console.log(`第一次请求成功,接着请求第二次${res.code}`)
foo('success').then(res => {
console.log(`第二次请求成功,接着请求第三次${res.code}`)
foo('success').then(res => {
console.log(`第三次请求成功,接着请求第四次${res.code}`)
foo('success').then(res => {
console.log(`第四次请求成功,接着请求第五次${res.code}`)
foo('success').then(res => {
console.log(`第五次请求成功,接着请求第六次${res.code}`)
foo('failture').then(res => {
}, err => {
console.log(`第六次请求失败gggg${err.code}`)
})
}, err => { })
}, err => { })
}, err => { })
}, err => { })
}, err => { })
上面的代码属于把奔驰当做拖拉机在开,还是会有回调地狱的存在,接下来我们看看,Promise是如何解决回调地狱的。
使用Promise重构回调函数(二)
function foo(url) {
return new Promise((resolve, reject) => {
// 假设这是网络请求,需要请求完毕才能返回给用户使用
setTimeout(() => {
if (url === 'success') {
resolve({ code: '200', message: '请求成功' })
} else {
reject({ code: '-200', message: '请求失败' })
}
}, 1000)
})
}
foo('success')
.then(res => {
console.log(`第一次请求成功,接着请求第二次${res.code}`)
return foo('success')
}, err => {
console.log(`第一次请求失败,还是会执行下面的代码${err.code}`)
})
.then(res => {
console.log(`第二次请求成功,接着请求第三次${res.code}`)
return foo('success')
}, err => {
console.log(`第二次请求失败,还是会执行下面的代码${err.code}`)
})
.then(res => {
console.log(`第三次请求成功,接着请求第四次${res.code}`)
return foo('success')
}, err => {
console.log(`第三次请求失败,还是会执行下面的代码${err.code}`)
})
.then(res => {
console.log(`第四次请求成功,接着请求第五次${res.code}`)
// 这里让他失败!
return foo('failture')
}, err => {
console.log(`第四次请求失败,还是会执行下面的代码${err.code}`)
})
.then(res => {
console.log(`第五次请求成功,接着请求第六次${res.code}`)
return foo('success')
}, err => {
console.log(`第五次请求失败,还是会执行下面的代码${err.code}`)
})
.then(res => {
console.log(`第六次请求成功,接口请求完毕${res.code}`)
return foo('success')
}, err => {
console.log(`第六次请求失败,还是会执行下面的代码${err.code}`)
})
这里我们发现,我们解决了回调地狱的问题,但是出现了新的问题,代码量增加了很多。而且代码执行的中途遇见了错误,代码还是会继续执行。我们尝试使用catch(统一捕获错误)优化一下代码。
使用Promise重构回调函数(三)
function foo(url) {
return new Promise((resolve, reject) => {
// 假设这是网络请求,需要请求完毕才能返回给用户使用
setTimeout(() => {
if (url === 'success') {
resolve({ code: '200', message: '请求成功' })
} else {
reject({ code: '-200', message: '请求失败' })
}
}, 1000)
})
}
foo('success')
.then(res => {
console.log(`第一次请求成功,接着请求第二次${res.code}`)
return foo('success')
})
.then(res => {
console.log(`第二次请求成功,接着请求第三次${res.code}`)
return foo('success')
})
.then(res => {
console.log(`第三次请求成功,接着请求第四次${res.code}`)
return foo('success')
})
.then(res => {
console.log(`第四次请求成功,接着请求第五次${res.code}`)
return foo('success')
})
.then(res => {
console.log(`第五次请求成功,接着请求第六次${res.code}`)
// 故意让他失败
return foo('failture')
})
.then(res => {
console.log(`第六次请求成功,接着请求第七次${res.code}`)
return foo('success')
})
.catch(err => { console.log(`反正就是报错了,由我统一来捕获错误,捕获到错误代码不会继续运行。`) })
使用catch统一捕获错误的优点就是代码量减少了,而且代码执行中途遇见错误,代码就不会继续往下面执行了,因为返回了一个reject被最后的catch给捕获了,但是这还不是最终的一个处理异步的方案。接下来我们来看看最终的一个解决方案。
使用Promise重构回调函数(四)
function foo(url) {
return new Promise((resolve, reject) => {
// 假设这是网络请求,需要请求完毕才能返回给用户使用
setTimeout(() => {
if (url === 'success') {
resolve({ code: '200', message: '请求成功' })
} else {
reject({ code: '-200', message: '请求失败' })
}
}, 1000)
})
}
async function getData() {
try {
const res1 = await foo('success')
console.log(`第一次请求成功,接着请求第二次${res1.code}`)
const res2 = await foo('success')
console.log(`第二次请求成功,接着请求第三次${res2.code}`)
const res3 = await foo('success')
console.log(`第三次请求成功,接着请求第四次${res3.code}`)
const res4 = await foo('failture')
console.log(`第四次请求成功,接着请求第五次${res4.code}`)
const res5 = await foo('success')
console.log(`第五次请求成功,接着请求第六次${res5.code}`)
const res6 = await foo('success')
console.log(`第六次请求成功,接着请求第七次${res6.code}`)
console.log(`---------------------------end--------------------------------`)
} catch (err) {
console.log(`反正就是失败了,如果遇见失败,代码不会继续向下执行`)
}
}
getData()
我们使用了async和await,来解决回调地狱、then调用代码冗余问题,这是一个最终的解决方案,在实际开发项目中,我们都是使用的这个方案。