async / await:更好的異步解決方案
在實際開發中總會遇到許多異步的問題,最常見的場景接口請求之后一定要等一段時間才能得到結果,如果遇到多個接口前后依賴,那么問題就變得復雜。大家都一直在嘗試使用更好的方案來解決這些問題。最開始只能利用回調函數,后來開始有人使用Promise的思維來搞定。到ES6中開始支持原生的Promise,引入Generator函數。
直到ES7,有了async/await。
這是一個用同步的思維來解決異步問題的方案。
我想很多人可能還不太分得清同步與異步的區別。如果你已經徹底了解了事件循環,那么想必對異步的概念應該非常了解。當我們發出了請求,并不會等待響應結果,而是會繼續執行后面的代碼,響應結果的處理在之后的事件循環中解決。那么同步的意思,就是等結果出來之后,代碼才會繼續往下執行。
我們可以用一個兩人問答的場景來比喻異步與同步。A向B問了一個問題之后,不等待B的回答,接著問下一個問題,這是異步。A向B問了一個問題之后,然后就笑呵呵的等著B回答,B回答了之后他才會接著問下一個問題。
那么我們先記住這個特點,async/await使用同步的思維,來解決異步的問題。在繼續講解它的語法與使用之前,我們先介紹一下如何在我們的開發環境中支持該語法。
如果你已經知道如何配置,可跳過
一、如何在自己的開發環境中支持async/await語法
這里主要介紹兩種方式。
1. webpack中支持該語法
首先在當前項目中使用npm下載babel-loader。
- > npm install babel-loader --save-dev
然后在配置文件webpack.confing.dev.js中配置,在module.exports.module.rules中添加如下配置元素即可。
- {
- test: /\.(js|jsx)$/,
- include: paths.appSrc,
- loader: require.resolve('babel-loader'),
- options: {
- cacheDirectory: true,
- },
- },
如果你使用***版本的create-react-app或者vue-cli來構建你的代碼,那么它們應該已經支持了該配置。
2. gulp中支持該語法
首先安裝gulp插件
- > npm install gulp-babel --save-dev
然后編寫任務
- var gulp = require('gulp');
- var babel = require('gulp-babel');
- gulp.task('babel', function() {
- return gulp.src('src/app.js')
- .pipe(babel())
- .pipe(gulp.dest('dist'));
- });
二、如何使用
async函數是Generator的一個語法糖。如果你不知道Generator是什么函數也沒有關系,我們只需要知道async函數實際上返回的是一個Promise對象即可。
- async function fn() {
- return 30;
- }
- // 或者
- const fn = async () => {
- return 30;
- }
在聲明函數時,前面加上關鍵字async,這就是async的用法。當我們用console.log打印出上面聲明的函數fn,我們可以看到如下結果:
- console.log(fn());
- // result
- Promise = {
- __proto__: Promise,
- [[PromiseStatus]]: "resolved",
- [[PromiseValue]]: 30
- }
很顯然,fn的運行結果其實就是一個Promise對象。因此我們也可以使用then來處理后續邏輯。
- fn().then(res => {
- console.log(res); // 30
- })
await的含義為等待。意思就是代碼需要等待await后面的函數運行完并且有了返回結果之后,才繼續執行下面的代碼。這正是同步的效果。
但是我們需要注意的是,await關鍵字只能在async函數中使用。并且await后面的函數運行后必須返回一個Promise對象才能實現同步的效果。
當我們使用一個變量去接收await的返回值時,該返回值為Promise中resolve出來的值。
- // 定義一個返回Promise對象的函數
- function fn() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- resolve(30);
- }, 1000);
- })
- }
- // 然后利用async/await來完成代碼
- const foo = async () => {
- const t = await fn();
- console.log(t);
- console.log('next code');
- }
- foo();
- // result:
- // 30
- // next code
運行這個例子我們可以看出,當在async函數中,運行遇到await時,就會等待await后面的函數運行完畢,而不會直接執行next code。
如果我們直接使用then方法的話,想要達到同樣的結果,就不得不把后續的邏輯寫在then方法中。
- const foo = () => {
- return fn().then(t => {
- console.log(t);
- console.log('next code');
- })
- }
- foo();
很顯然如果使用async/await的話,代碼結構會更加簡潔,邏輯也更加清晰。
異常處理
在Promise中,我們知道是通過catch的方式來捕獲異常。而當我們使用async時,則通過try/catch來捕獲異常。
- function fn() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- reject('some error.');
- }, 1000);
- })
- }
- const foo = async () => {
- try {
- await fn();
- } catch (e) {
- console.log(e); // some error
- }
- }
- foo();
如果有多個await函數,那么只會返回***個捕獲到的異常。
- function fn1() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- reject('some error fn1.');
- }, 1000);
- })
- }
- function fn2() {
- return new Promise((resolve, reject) => {
- setTimeout(() => {
- reject('some error fn2.');
- }, 1000);
- })
- }
- const foo = async () => {
- try {
- await fn1();
- await fn2();
- } catch (e) {
- console.log(e); // some error fn1.
- }
- }
- foo();
實踐
在實踐中我們遇到異步場景最多的就是接口請求,那么這里就以jquery中的$.get為例簡單展示一下如何配合async/await來解決這個場景。
- // 先定義接口請求的方法,由于jquery封裝的幾個請求方法都是返回Promise實例,因此可以直接使用await函數實現同步
- const getUserInfo = () => $.get('xxxx/api/xx');
- const clickHandler = async () => {
- try {
- const resp = await getUserInfo();
- // resp為接口返回內容,接下來利用它來處理對應的邏輯
- console.log(resp);
- // do something
- } catch (e) {
- // 處理錯誤邏輯
- }
- }
為了保證邏輯的完整性,在實踐中try/catch必不可少。總之,不處理錯誤邏輯的程序員不是好程序員。
與Promise相比,個人認為async/await有一定的簡潔性,但也并非就比Promise有絕對的優勢,因此只能算是提供了另外一種稍好的方式,至于大家學習之后選擇哪種方式來解決自己的問題,這僅僅只是你的個人喜好問題。