Giter Site home page Giter Site logo

until's Introduction

Latest release

until

Gracefully handle a Promise using async/await.

Why?

With the addition of async/await keywords in ECMAScript 2017 the handling of Promises became much easier. However, one must keep in mind that the await keyword provides no standard error handling API. Consider this usage:

async function getUser(id) {
  const data = await fetchUser(id)
  // Work with "data"...
}

In case fetchUser() throws an error, the entire getUser() function's scope will terminate. Because of this, it's recommended to implement error handling using try/catch block wrapping await expressions:

async function getUser(id){
  let data = null

  try {
    data = await asyncAction()
  } catch (error) {
    console.error(error)
  }

  // Work with "data"...
}

While this is a semantically valid approach, constructing try/catch around each awaited operation may be tedious and get overlooked at times. Such error handling also introduces separate closures for execution and error scenarios of an asynchronous operation.

This library encapsulates the try/catch error handling in a utility function that does not create a separate closure and exposes a NodeJS-friendly API to work with errors and resolved data.

Getting started

Install

npm install @open-draft/until

Usage

import { until } from '@open-draft/until'

async function getUserById(id) {
  const { error, data } = await until(() => fetchUser(id))

  if (error) {
    return handleError(error)
  }

  return data
}

Usage with TypeScript

import { until } from '@open-draft/until'

interface User {
  firstName: string
  age: number
}

interface UserFetchError {
  type: 'FORBIDDEN' | 'NOT_FOUND'
  message?: string
}

async function getUserById(id: string) {
  const { error, data } = await until<UserFetchError, User>(() => fetchUser(id))

  if (error) {
    return handleError(error.type, error.message)
  }

  return data.firstName
}

Frequently asked questions

Why does until accept a function and not a Promise directly?

This has been intentionally introduced to await a single logical unit as opposed to a single Promise.

// Notice how a single "until" invocation can handle
// a rather complex piece of logic. This way any rejections
// or exceptions happening within the given function
// can be handled via the same "error".
const { error, data } = until(async () => {
  const user = await fetchUser()
  const nextUser = normalizeUser(user)
  const transaction = await saveModel('user', user)

  invariant(transaction.status === 'OK', 'Saving user failed')

  return transaction.result
})

if (error) {
  // Handle any exceptions happened within the function.
}

Why does until return an object and not an array?

The until function used to return an array of shape [error, data] prior to 2.0.0. That has been changed, however, to get proper type-safety using discriminated union type.

Compare these two examples:

const [error, data] = await until(() => action())

if (error) {
  return null
}

// Data still has ambiguous "DataType | null" type here
// even after you've checked and handled the "error" above.
console.log(data)
const result = await until(() => action())

// At this point, "data" is ambiguous "DataType | null"
// which is correct, as you haven't checked nor handled the "error".

if (result.error) {
  return null
}

// Data is strict "DataType" since you've handled the "error" above.
console.log(result.data)

It's crucial to keep the entire result of the Promise in a single variable and not destructure it. TypeScript will always keep the type of error and data as it was upon destructuring, ignoring any type guards you may perform later on.

Special thanks

until's People

Contributors

bnzone avatar craig-davis avatar dependabot[bot] avatar karlhorky avatar kettanaito avatar mattcosta7 avatar mohamedwagih avatar nick-w-nick avatar singhamandeep007 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

until's Issues

Alternative Tip/Suggestion

I saw someone using this library and it was confusing to me at first glance of what it was doing.
Then i figured: Is until needed, are there alternative solutions? And there is...

This dose not do exactly the same thing until is doing, but you won't need any try/catch or dependencies with the Settle method.

const [{status, value}] = await Promise.allSettled([fetchUser(id)])

the output is either:

{ status: "fulfilled", value: user } // or
{ status: "rejected",  reason: Error('an error') }

So the signature changes a bit. instead of checking for an error object u would have to check if status is fulfilled or rejected

-  const [error, user] = await until(() => fetchUser(id))
-
-  if (error) {
-    return handleError(error)
-  }

+  const [{status, value: user}] = await Promise.allSettled([fetchUser(id)])
+
+  if (status === 'rejected') {
+    return handleError(error)
+  }

user.password = 'abc'
await user.$save()

Sry if this is not an issue or bug, just wanted to share some stuff, you can close this - shares

Incorrect Types

Hi @kettanaito! ๐Ÿ‘‹ Hope you are well :)

The return type doesn't agree with the null values returned here:

export const until = async <DataType = unknown, ErrorType = Error>(promise: () => Promise<DataType>): Promise<[ErrorType, DataType]> => {
  try {
    const data = await promise().catch((error) => {
      throw error
    })
    return [null, data] // ๐Ÿ’ฅ Type 'null' is not assignable to type 'ErrorType'.
  } catch (error) {
    return [error, null] // ๐Ÿ’ฅ Type 'null' is not assignable to type 'DataType'.
  }
}

Created a playground for this: https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=10&pc=2#code/AQQwzgngdgxsBmBXWAXAlgeysZ6A2wAPACIgogAqEADgKbAC8OUA1lBgO5QA0wAogCcBGAVTqN+QkQD4AFNWEBbNGFoAuYLICUjacAAKSlbRJlKNWtK0bDGZasIBtQcNEXepcmNoBdPcABvAChgYBQBCECQ0OAYLDAUYAATMwkQDhA0RIU7Y20AOhgyGAALWVlaKQEdBj1gmJiUEuEOYErXaNCAXy1O4AFaFEQBbEcoRDw8XhTyH2iu2OKSzXaRHXqYgaGR4EdVgV5xybnuoK6gA

Question: Why use .catch on the promise

I am curious why this change was introduced.

// Original implementation  
export const until = async <DataType = unknown, ErrorType = Error>(promise: () => Promise<DataType>): Promise<[ErrorType, DataType]> => {
  try {
    const data = await promise()
    return [null, data]
  } catch (error) {
    return [error, null]
}
// Current implementation 
export const until = async <DataType = unknown, ErrorType = Error>(promise: () => Promise<DataType>): Promise<[ErrorType, DataType]> => {
  try {
   const data = await promise().catch((error) => { // .catch added
      throw error
    })
    return [null, data]
  } catch (error) {
    return [error, null]
}

Is there a benefit of calling .catch on the promise? It is my understanding that the catch block would handle throws?

Revert to array return type

The old versions of TS had trouble creating a discriminated union type out of an array tuple if you destructure it. That's not the case for the latest TS 4.9. We can use the array-based pattern again.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.