JS中如何優雅的使用async await詳解
jQuery的$.ajax
在開始之前我們先來聊聊我的js異步之路。在我還在學校的時候,那時候還是 jQuery 的天下,我直接接觸到並且經常使用的異步操作就是網絡請求,一手 $.ajax 走天下,伴我過瞭大二到畢業後差不多大半年的時間。
$.ajax( "/xxx" ) .done(function() { // success !!! do something... }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. });
不可否認,$.ajax 這個東西還是挺好使的,在面對大部分場景隻有一個請求的情況下,完全勝任甚至覺得很棒
但是有個大大的問題,那就是面對請求鏈的時候就會特別特別的糟心,比如一個請求依賴於另一個請求的結果,兩個可能還無所謂,要是五個八個的,可能想要直接自殺。。。
$.ajax('/xxx1') .done(function() { // success !!! do something... $.ajax('/xxx2') .done(function() { // success !!! do something... $.ajax('/xxx3') .done(function() { // success !!! do something... $.ajax('/xxx4') .done(function() { // success !!! do something... $.ajax('/xxx5') .done(function() { // success !!! do something... // more... }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. }); }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. }); }) .fail(function() { // fail !!! do something... $.ajax('/xxx6') .done(function() { // success !!! do something... $.ajax('/xxx7') .done(function() { // success !!! do something... // more.... }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. }); }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. }); }) .always(function() { // loading finished.. }); }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. }); }) .fail(function() { // fail !!! do something... }) .always(function() { // loading finished.. });
抱歉,我不知道你可以套這麼多層。。。,但事實就是TM經常出現這樣的流程,大夥兒說說,這不能怪產品吧???隻能怪自己學藝不精
像這樣鏈式操作,我覺得吧,是個人可能都是奔潰的,先不說代碼的可讀性,就拿天天在變化的產品需求來說,也許先前是 請求1 結束之後緊接著 請求2 、 請求3 ,後面產品大手一揮,我覺得這個流程不大對,後面就變成瞭 請求2、 請求3 、 請求1,這尼瑪套娃怎麼改?可能有人會有疑問,為啥不用 axios 、 await 、async 呢?這個就不得不提項目代碼是08年開寫的JSP瞭。。。。在整瞭大半年的屎上拉屎以後,迎來瞭大大的轉機,新寫的項目開始往 Vue 上面轉,並且放棄一部分兼容性,我TM直接起飛。。。
Webpack時代的開始
新的項目直接Vue + Webpack,我直接就給安排上 axios 、 await 、async ,現在代碼非常好使,嵌套N層的代碼沒瞭
const r1 = await doSomthing1(); if (r1.xxx === 1) { const r2 = await doSomthing2(r1); const r3 = await doSomthing3(r2); // do something.... } else { const r4 = await doSomthing4(r1); const r5 = await doSomthing5(r4); // do something.... } // do something....
但是上面的代碼存在一個問題,如果某個任務報錯,那麼代碼直接就終止瞭。。。這樣不符合我們的預期啊,那我們加上 try catch
let r1; try { r1 = await doSomthing1(); } catch (e) { // do something... return; } if (r1) { if (r1.xxx === 1) { let r2; try { r2 = await doSomthing2(r1); } catch (e) { // do something... return; } if (r2) { let r3; try { r3 = await doSomthing3(r2); } catch (e) { // do something... return; } // do something... } } else { let r4; try { r4 = await doSomthing4(r1); } catch (e) { // do something... return; } if (r4) { let r5; try { r5 = await doSomthing5(r4); } catch (e) { // do something... return; } } // do something... } // do something... }
???
優化瞭,等於沒優化。。。
這時候我想聰明的小夥伴可能會說瞭,這是啥煎餅玩意兒。而呆滯的小夥伴已經開始想怎麼解決這樣的問題瞭。。。
深入瞭解Promise
我們來看一下 Promise 的定義
/** * Represents the completion of an asynchronous operation */ interface Promise<T> { /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>; /** * Attaches a callback for only the rejection of the Promise. * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of the callback. */ catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>; }
then 和 catch 都會返回一個新的 Promise ,我相信很多小夥伴都已經想到瞭怎麼解決方法,需要使用 try catch 是因為它會報錯,那我們返回一個 永遠不會報錯的結果 不就行瞭?說幹就幹
消滅嵌套
function any(promise) { return promise.then((v) => v).catch((_) => null); }
這樣就完全解決瞭啊???通過判斷是否有值來判斷是否成功,就不用再寫 try catch 瞭,但是這樣的代碼有點不大好使,如果 then 返回的是一個 void 那麼就完犢子瞭,一個 undefined 一個 null ,這還判斷個錘子,我們再來改進一下
function any(promise) { return promise .then((v) => ({ ok: v, hasErr: false })) .catch((e) => ({ err: e, hasErr: true })); }
使用的話
const r = await any(doSomething()); if (r.hasErr) { console.log(r.err); return; } console.log(r.ok);
現在看起來是不是很完美呢,趕緊和小夥伴推銷一下。
小夥伴:???這啥煎餅玩意兒,不用不用。
我:這個我寫的,在異步中用起來很好使的,告別嵌套 try catch ,巴拉巴拉。。。
小夥伴:好的,下次一定用。
大傢肯定有遇到過這樣的情況,大傢寫的代碼互相看不起,隻要不是三方庫,大傢都是能不用同事寫的就不用。。。
await-to-js
我都以為隻有我一人欣賞,這一份優雅。事情出現轉機,某天我正在刷github,發現瞭一個和我差不多異曲同工之妙的東西 await-to-js ,幾行代碼透露瞭和我一樣的執著
// 下面是最新的代碼 /** * @param { Promise } promise * @param { Object= } errorExt - Additional Information you can pass to the err object * @return { Promise } */ export function to<T, U = Error> ( promise: Promise<T>, errorExt?: object ): Promise<[U, undefined] | [null, T]> { return promise .then<[null, T]>((data: T) => [null, data]) .catch<[U, undefined]>((err: U) => { if (errorExt) { Object.assign(err, errorExt); } return [err, undefined]; }); } export default to;
再貼上使用示例
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'); }
是不是感覺回來瞭,嵌套不再。。。
為瞭讓小夥伴用上一行的代碼,我隻能忍痛推薦 await-to-js ,發上github地址,小夥伴:八百多star (ps: 現在2K+) 質量可靠,看瞭一下示例,嗯嗯,很不錯,很完美,後面。。。後面的事不用我多說瞭,我自己寫的也全換成瞭 await-to-js 。。。
我待世界如初戀,初戀卻傷我千百遍
總結
我實現的版本其實存在著一點點問題的,在JS這樣 靈活 的語言中,我改瞭返回值,別人就能直接抄我的傢,類型不夠嚴謹,要是放TS裡,那就隻能說一點小毛病,新加瞭 ok 、 err 、 hasErr 增加瞭一小點點case,但並不致命
await-to-js 中一點點的設計哲學,為啥把錯誤放在數組的第一個位置,而不是把成功放在第一個位置,就很明示:永遠謹記錯誤,把錯誤放在第一位,而不是很 自信 成功,就忘記錯誤的慘痛。
const [, result] = await to(iWillSucceed());
參考資料
- $.ajax
- Promise
- await-to-js
到此這篇關於JS中如何優雅的使用async await的文章就介紹到這瞭,更多相關JS優雅使用async await內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Javascript中異步等待的深入理解
- Vue中的同步調用和異步調用方式
- 使用async await處理錯誤方法示例
- 前端常見面試題之async/await和promise的區別
- await-to-js源碼深入理解處理異步任務用法示例