回不去惹,用 await-to-js 讓你的 Promise 操作簡潔又俐落
前陣子意外注意到一個有趣的套件,名為 await-to-js。
主要用途是多一種 Promise 與 await 的寫法,可以在特定情境下讓程式碼更簡潔。
類似的實作其實 Radash 也有,不過 await-to-js 用法比較單純。
2024/08/26 更新
ECMAScript 新提案: Safe Assignment Operator Proposal
等於直接把 await-to-js 加到語言規格中,期待 await-to-js 套件退休那一天。(๑•̀ㅂ•́)و✧
ECMAScript 新提案: Safe Assignment Operator Proposal
等於直接把 await-to-js 加到語言規格中,期待 await-to-js 套件退休那一天。(๑•̀ㅂ•́)و✧
具體差在哪?
假設原本的 Promise 是這樣:
let isLoading = true;
task().then(() => {
console.log('done');
}).catch(() => {
console.error('error');
}).finally(() => {
isLoading = false;
});
用 await 會這樣寫。
let isLoading = true;
try {
await task()
console.log('done');
} catch(){
console.error('error');
}
isLoading = false;
使用 await-to-js 改寫後變成:
import to from 'await-to-js';
let isLoading = true;
const [error] = await to(task())
isLoading = false;
if (error) {
console.error('error');
return;
}
console.log('done');
除了看起來比較簡潔,不用 try catch 外,配合 early return 很有效。
在前端可能還好,在後端各種 Promise 操作的場合的很方便,從此以後寫法再也回不去了。XD
以下是官方的範例。
import to from 'await-to-js';
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');
}
我自己的話後端比較常用,例如 NestJS 的 Controller 中:
@Controller()
export class ArticleController {
...
@UseGuards(AuthGuard('jwt'))
@Post('articles')
async getInfo(
@User() jwtPayload: JwtPayload,
@Body() body: CreateArticleDto,
) {
const [userError, result] = await to(
this.userService.check(jwtPayload)
);
if (userError) {
throw new HttpException(
`使用者身分異常`,
HttpStatus.FORBIDDEN,
);
}
const [checkError, result] = await to(
this.articleService.checkQuota(jwtPayload)
);
if (checkError) {
throw new HttpException(
`建立文章失敗,請稍後再試`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
if(result.reason === 'quota-has-been-used-up') {
throw new HttpException(
`文章額度已滿,請聯絡客服`,
HttpStatus.BAD_REQUEST,
);
}
const [createError, article] = await to(
this.articleService.create(body)
);
if (createError) {
throw new HttpException(
`建立文章失敗,請稍後再試`,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
return article;
}
}
可以提前處理 error,更方便使用 early return。◝(≧∀≦)◟
await-to-js 的實現很簡單,他的原始碼只有 22 行。
/**
* @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) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
});
}
export default to;
如果不想 npm install 的話,也可以直接貼到自己的專案使用。ヾ(◍'౪`◍)ノ゙
Promise 都改用 await to 嗎?
當然不是這樣啦,await to 只是多一個選擇。ლ(╹ε╹ლ)
實際上還是有些地方更適合使用 then 或 try catch。
例如:
有時候根本就不會有錯誤,就維持 await。
const result = await task();
有錯誤也沒關係,不需要特別處理,用 catch 紀錄一下就行。
await task().catch(console.error);
單一錯誤不重要,統一處理即可,就直接 try catch。
try {
await task1();
await task2();
await task3();
await task4();
} catch (error) {
console.error(error);
}
請依照實際情況挑選適合的寫法喔。(。・∀・)ノ゙
總結 🐟
- await-to-js 可以在特定情境下簡化 Promise 寫法
- 配合 early return 很有效
- 請不要只用 await-to-js,需要用 then 或 try catch 還是用原本的寫法