之前阅读过一篇文章《Async/Await替代Promise的6个理由》,现在async / await语法已经处于Stage3阶段。
兼容性
- 服务端方面,在
Node.js 7.6
版本后,async / await
语法已经被Node.js
支持,如Koa2
已经抛弃generator/yield语法,拥抱async / await
语法。 - 客户端方面,也可以通过
Babel
让我们随心所欲地使用最新的Ecmascript
语法。
从业务说起
单从我个人来讲,是不太喜欢使用这样的最新语法的。一直以来写代码还是拥抱Promise
为主。但自从用上了async / await
语法后好像就会对这种语法产生依赖感(看起来就像是同步阻塞的代码一样,还不用像generator
语法那样手动释放,或使用co
那样的流程控制库)
看下面这些常见的业务场景:
- 假设我们在编写一个刷题的程序,这个程序是智能的(对,它懂你),你每次做完一道题并提交,服务端会根据你提交的结果来判断你有没有掌握当前知识点,如果没有掌握,服务端会再次给你推送下一道题。
- 假设我们在编写一个用户手动对图像进行鉴别的软件,服务端给了两个接口,接口submit用于提交上一张图像的鉴别结果,只有当接口submit提交成功,才能去请求接口next,用于请求下一张图片的链接。
const requestSubmit = () => {
return new Promise((resolve, reject) => {
$.ajax({
url: '/submit',
success: (data) => {
resolve(data);
},
error: (err) => {
reject(err);
},
});
});
};
const requestNext = () => {
return new Promise((resolve, reject) => {
$.ajax({
url: '/next',
success: (data) => {
resolve(data);
},
error: (err) => {
reject(err);
},
});
});
};
我们如果使用Promise
语法:
requestSubmit()
.then((data) => {
// dosomething
return requestNext();
})
.then((data) => {
// dosomething
})
.catch((err) => {
console.log(err);
});
虽然使用Promise
,catch
只能捕获到最近一次Promise.then
的错误,但是实际上现在,其实可以这么捕获每次的错误!
不要忘记Promise.then
的第二个参数:
requestSubmit()
.then((data) => {
// dosomething
return requestNext();
}, (err) => {
console.log(err);
})
.then((data) => {
// dosomething
},(err) => {
console.log(err);
});
现在有一个问题,服务端可能会校验用户登录态,或参数是否正确等情况。如果用户登录态失效,会在第一个ajax
请求中,仍会走success
回调,并不会抛出错误,此时还是得去手动修改requestSubmit
函数。
此外,Promise.catch
方法只会捕获最近一次错误,那这个错误究竟在哪里抛出,其实并不得而知。
如:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
})
}
makeRequest()
.catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
但是注意:原文其实说得不全,我们这么捕获Promise
的错误是可以的:
const makeRequest = () => {
return callAPromise()
.then(() => callAPromise(), err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => callAPromise(), err => {
console.log(err);
})
.then(() => {
throw new Error("oops");
}, err => {
console.log(err);
});
}
makeRequest();
如果把上述业务场景改为async / await
语法:
(async function() {
try {
const res1 = await axios.post('/submit');
// dosomething
} catch (err) {
console.log(err);
} finally {
console.log('go next!');
}
try {
const res2 = await axios.get('/next');
// dosomething
} catch (err) {
console.log(err);
} finally {
console.log('done!');
}
})();
无论从异常捕获方面还是从代码可读性方面,都会感觉更胜一筹。
优势
这里其实大多数就是捡取那篇文章:
简洁
使用async / await
明显节约了不少代码。我们不需要写.then
,不需要写匿名函数处理Promise
的resolve
值,也不需要定义多余的data变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。
错误处理
async / await
让try / catch
可以同时处理同步和异步错误。在下面的Promise
示例中,try / catch
不能处理JSON.parse
的错误,因为它在Promise
中。我们需要使用.catch
,这样错误处理代码非常冗余。并且,在我们的实际生产代码会更加复杂。
const makeRequest = () => {
try {
getJSON()
.then(result => {
// JSON.parse可能会出错
const data = JSON.parse(result)
console.log(data)
})
// 取消注释,处理异步代码的错误
// .catch((err) => {
// console.log(err)
// })
} catch (err) {
console.log(err)
}
}
使用async / await
的话,catch
能处理JSON.parse
错误:
const makeRequest = async () => {
try {
// this parse may fail
const data = JSON.parse(await getJSON())
console.log(data)
} catch (err) {
console.log(err)
}
}
条件语句
这一点在上边也说了:
下面示例中,需要获取数据,然后根据返回数据决定是直接返回,还是继续获取更多的数据。
const makeRequest = () => {
return getJSON()
.then(data => {
if (data.needsAnotherRequest) {
return makeAnotherRequest(data)
.then(moreData => {
console.log(moreData)
return moreData
})
} else {
console.log(data)
return data
}
})
}
这些代码看着就头痛。嵌套(6层),括号,return
语句很容易让人感到迷茫,而它们只是需要将最终结果传递到最外层的Promise
。
上面的代码使用async / await
编写可以大大地提高可读性:
const makeRequest = async () => {
const data = await getJSON()
if (data.needsAnotherRequest) {
const moreData = await makeAnotherRequest(data);
console.log(moreData)
return moreData
} else {
console.log(data)
return data
}
}
中间值
你很可能遇到过这样的场景,调用promise1,使用promise1返回的结果去调用promise2,然后使用两者的结果去调用promise3。你的代码很可能是这样的:
const makeRequest = () => {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
如果promise3
不需要value1
,可以很简单地将promise
嵌套铺平。如果你忍受不了嵌套,你可以将value 1 & 2
放进Promise.all
来避免深层嵌套:
const makeRequest = () => {
return promise1()
.then(value1 => {
return Promise.all([value1, promise2(value1)])
})
.then(([value1, value2]) => {
return promise3(value1, value2)
})
}
错误栈
这里可见上边真实的业务例子。
调试
async/await
能够使得代码调试更简单。2个理由使得调试Promise
变得非常痛苦:
- 不能在返回表达式的箭头函数中设置断点
- 如果你在
.then
代码块中设置断点,使用Step Over
快捷键,调试器不会跳到下一个.then
,因为它只会跳过异步代码。使用await / async
时,你不再需要那么多箭头函数,这样你就可以像调试同步代码一样跳过await
语句。