---
url: /blog-program/await-to-js-keeps-your-promises-slick.md
description: 可以在特定情境下簡化 Promise 寫法
---

![await-to-js](/await-to-js.webp){.cover}

# 回不去惹，用 await-to-js 讓你的 Promise 操作簡潔又俐落

前陣子意外注意到一個有趣的套件，名為 [await-to-js](https://www.npmjs.com/package/await-to-js)。

主要用途是多一種 Promise 與 await 的寫法，可以在特定情境下讓程式碼更簡潔。

類似的實作其實 [Radash](https://radash-docs.vercel.app/docs/async/tryit) 也有，不過 await-to-js 用法比較單純。

::: tip 2024/08/26 更新
[ECMAScript 新提案： Safe Assignment Operator Proposal](https://dev.to/dharamgfx/bye-bye-try-catch-blocks-meet-javascripts-safe-assignment-operator-proposal-1j7?ref=dailydev)

等於直接把 await-to-js 加到語言規格中，期待 await-to-js 套件退休那一天。(๑•̀ㅂ•́)و✧
:::

## 具體差在哪？

假設原本的 Promise 是這樣：

```ts
let isLoading = true
task().then(() => {
  console.log('done')
}).catch(() => {
  console.error('error')
}).finally(() => {
  isLoading = false
})
```

用 await 會這樣寫。

```ts
let isLoading = true
try {
  await task()
  console.log('done')
}
catch (e) {
  console.error('error')
}
isLoading = false
```

使用 await-to-js 改寫後變成：

```ts
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

以下是官方的範例。

```ts
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 中：

```ts
@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 行。

```ts
/**
 * @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。

```ts
const result = await task()
```

有錯誤也沒關係，不需要特別處理，用 catch 紀錄一下就行。

```ts
await task().catch(console.error)
```

單一錯誤不重要，統一處理即可，就直接 try catch。

```ts
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 還是用原本的寫法
