看过很多讲 promise
和 async
的文章,但是一直看一直忘,于是打算弄几句口诀来帮助记忆;另外,结合自己的一些项目经验,谈一下,async
在实际项目中如何更优雅的使用
口诀:
- 如果.then内部所指定的成功或者失败的回调函数的返回值,是一个非promise值,那么外层的这个.then 所返回的promise状态就为成功的,值就为返回的非promise值
2.如果返回的本身就是一个promise值, 那么就把这个值作为外侧.then 所返回的promise实例的值,如果返回一个pedding状态的promise, 那么外侧的.then,也会返回一个pedding状态的promise实例(
用于中断promise
),如果.then的回调函数里面里面抛出了异常,那么.then所返回的promise的状态就为失败的, 失败的原因就为抛出的异常
- await 只能等待成功的promise
先从 promise 说起
show me the code
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(
response => {console.log('联系服务器成功了',response)},
error => {console.log('联系服务器失败了',error)}
)
什么情况下会触发第一个 error
首先,思考一个问题, 什么时候会触发上面这个 error
回调?
只有联系服务器不成功,才会触发
,比如断网,超时
那 404
的时候,会触发吗?
fetch('https://jsonplaceholder.typicode.com/todos2222/1')
.then(
response => {console.log('联系服务器成功了',response)},
error => {console.log('联系服务器失败了',error)}
)
修改地址, 请求一个不存在的资源, 将看到 http 响应状态码为 404,并且还是显示 联系服务器成功了
, 为什么呢?
其实,联系服务器成不成功,和服务器能不能响应 todos2222 ,是两码事
。 会有这个误解的原因主要还是纯前端对于http状态的东西,只是浅显的了解。其实 返回 404
,甚至返回 10000
,都是后端开发人员自己写的, 只是有一个叫http状态码规范
的东西, 来约束前后端开发人员罢了。 所以 返回 404
, 其实还是联系上服务器了, 不同的http库,其对异常的理解也不一样, 例如axios, 所有非200状态码,都认为是异常
接下里获取数据
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(
response => {
console.log('联系服务器成功了')
return response.json()
},
error => {console.log('联系服务器失败了',error)}
).then(
response => {console.log('获取数据成功了', response)},
error => {console.log('获取数据失败了',error)}
)
以上代码, 其实是有问题的,当联系服务器失败
时, 还是会打印获取数据成功了
, 这样的代码是不能放到实际业务中的。比如: 有个充值的异步任务, 充值成功,才跳转到充值成功页面,而假设使用以上代码, 那么即使充值失败,也会跳转到支付成功页面。
为什么 prosime
会这样呢?
当联系服务器失败时, 程序来到失败回调: error => {console.log('联系服务器失败了',error)}
, 这个回调函数会返回 undefined
, 而undefined
属于非peomise值
,根据上面的口诀,当 .then
中的回调返回了 非promise值
时,外层的 .then
将返回成功的状态,值为 undifined
, 因此会触发response => {console.log('获取数据成功了', response)}
在处理后端返回的业务数据前, 除了需要处理 axios
,fetch
等api的第一个.then,也会有其他的 .then
, 比如做了 promise
封装,以便清洗一下返回参数,或者做统一的错误处理:比如弹窗显示,这样可以让我们不用在每一个具体的页面里再写一遍错误弹窗逻辑,所以非常需要实现的需求就是, 在前面的.then回调中出现错误时,不要再执行之后的.then回调了
解决方法:
-
返回
pedding
的promise
状态 -
把错误处理放到最后
-
使用
async
await
返回 pedding
的 promise
状态
让 .then
返回一个 peddiing
状态的 promise
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(
response => {
console.log('联系服务器成功了')
return response.json()
},
error => {
console.log('联系服务器失败了',error)
return new Promise(()=>{})
}
).then(
response => {console.log('获取数据成功了', response)},
error => {console.log('获取数据失败了',error)}
)
ps: 记住,new Promise(()=>{}) 需要至少传入一个回调
把错误放在最后处理
当promise
链某个.then
出现错误时,还会执行之后的.then
,原因就是我们返回了非promise值
,隐式的使外层的.then
返回了成功的状态,以至于后面的.then
中的成功回调会被调用,如果我们就不返回 非Promise值
, 比如说不给.then(()=>{},()=>{})
传递第二个回调函数,这样的话,.then
的状态就只由内部的第一个回调指定,但是第二个回调是处理异常的, 如果没有第二个回调,异常又怎么处理呢? 其实 promise
的错误会传递, 所以可以在最后处理错误。需要做错误的区分,有点麻烦
fetch('https://jsonplaceholder.typicode.com/todos/1')
.then(
response => {
console.log('联系服务器成功了')
return response.json()
},
).then(
response => {console.log('获取数据成功了', response)}
).catch(
error => {console.log('catch到错误:',error)}
)
使用 async await
因为await
只能等待 成功的promise
, 所以对于失败的promise
, 会中断代码的执行,这样也达到了中断promise
链的目的,之后再结合try{}catch(){}
处理异常, 即使做了try catch, 代码仍然会中断在异常的promise处
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
console.log('当网络中断时, 我不会执行')
const data = await response.json()
console.log(data)
try{
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
console.log('当网络中断时, 我不会执行')
const data = await response.json()
console.log(data)
}catch(err){
console.log('catch到错误:',err)
}
如果有人不喜欢使用 try{}catch(){}
处理异常,也可以试试,await-to-js, 也就是 uniapp
对 promise
的封装风格
import to from 'await-to-js';
// If you use CommonJS (i.e NodeJS environment), it should be:
// const to = require('await-to-js').default;
async function asyncTaskWithCb(cb) {
let err, user, savedTask, notification;
[ err, user ] = await to(UserModel.findById(1));
if(!user) return cb('No user found');
[ err, savedTask ] = await to(TaskModel({userId: user.id, name: 'Demo Task'}));
if(err) return cb('Error occurred while saving task');
if(user.notificationsEnabled) {
[ err ] = await to(NotificationService.sendNotification(user.id, 'Task Created'));
if(err) return cb('Error while sending notification');
}
if(savedTask.assignedUser.id !== user.id) {
[ err, notification ] = await to(NotificationService.sendNotification(savedTask.assignedUser.id, 'Task was created for you'));
if(err) return cb('Error while sending notification');
}
cb(null, savedTask);
}
async function asyncFunctionWithThrow() {
const [err, user] = await to(UserModel.findById(1));
if (!user) throw new Error('User not found');
}
选择 try{}catch(){}
还是 await-to-js
?
await-to-js
由于在内部把异常包装成非promise值
,因此只会返回成功的状态,假如 await
中发生错误,await
的下一行代码还是会执行, 所以需要在每一个 await
的下一行都做异常判断,并且使用 return 关键字
中断代码执行
我更倾向于简洁直观的代码风格 喜欢把所有 await
写在一起,这样阅读起来会比较清晰,至于异常处理部分,虽然需要做if(err.type)else{}
判断, 代码会显得凌乱一些,不过只在初次编写时会凌乱,之后维护时,基本也不会看这部分的代码 所以选择 try{}catch(){}
参考: