Gracefully handle Promises using async/await without try/catch.
Installations
npm install @open-draft/until
Developer
open-draft
Developer Guide
Module System
CommonJS, ESM
Min. Node Version
Typescript Support
Yes
Node Version
18.15.0
NPM Version
9.5.0
Statistics
531 Stars
53 Commits
11 Forks
5 Watching
1 Branches
8 Contributors
Updated on 21 Nov 2024
Languages
TypeScript (94.43%)
JavaScript (5.57%)
Total Downloads
Cumulative downloads
Total Downloads
330,973,470
Last day
-0.8%
773,988
Compared to previous day
Last week
4.7%
3,967,661
Compared to previous week
Last month
13.8%
16,374,814
Compared to previous month
Last year
42%
151,458,982
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Dev Dependencies
6
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:
1async function getUser(id) { 2 const data = await fetchUser(id) 3 // Work with "data"... 4}
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:
1async function getUser(id){ 2 let data = null 3 4 try { 5 data = await asyncAction() 6 } catch (error) { 7 console.error(error) 8 } 9 10 // Work with "data"... 11}
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
1npm install @open-draft/until
Usage
1import { until } from '@open-draft/until' 2 3async function getUserById(id) { 4 const { error, data } = await until(() => fetchUser(id)) 5 6 if (error) { 7 return handleError(error) 8 } 9 10 return data 11}
Usage with TypeScript
1import { until } from '@open-draft/until' 2 3interface User { 4 firstName: string 5 age: number 6} 7 8interface UserFetchError { 9 type: 'FORBIDDEN' | 'NOT_FOUND' 10 message?: string 11} 12 13async function getUserById(id: string) { 14 const { error, data } = await until<UserFetchError, User>(() => fetchUser(id)) 15 16 if (error) { 17 return handleError(error.type, error.message) 18 } 19 20 return data.firstName 21}
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
.
1// Notice how a single "until" invocation can handle 2// a rather complex piece of logic. This way any rejections 3// or exceptions happening within the given function 4// can be handled via the same "error". 5const { error, data } = until(async () => { 6 const user = await fetchUser() 7 const nextUser = normalizeUser(user) 8 const transaction = await saveModel('user', user) 9 10 invariant(transaction.status === 'OK', 'Saving user failed') 11 12 return transaction.result 13}) 14 15if (error) { 16 // Handle any exceptions happened within the function. 17}
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:
1const [error, data] = await until(() => action()) 2 3if (error) { 4 return null 5} 6 7// Data still has ambiguous "DataType | null" type here 8// even after you've checked and handled the "error" above. 9console.log(data)
1const result = await until(() => action()) 2 3// At this point, "data" is ambiguous "DataType | null" 4// which is correct, as you haven't checked nor handled the "error". 5 6if (result.error) { 7 return null 8} 9 10// Data is strict "DataType" since you've handled the "error" above. 11console.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 oferror
anddata
as it was upon destructuring, ignoring any type guards you may perform later on.
Special thanks
- giuseppegurgone for the discussion about the original
until
API.
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
Found 5/18 approved changesets -- score normalized to 2
Reason
9 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
- Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/open-draft/until/ci.yml/main?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/open-draft/until/ci.yml/main?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/open-draft/until/ci.yml/main?enable=pin
- Info: 0 out of 2 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 1 third-party GitHubAction dependencies pinned
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/ci.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 19 are checked with a SAST tool
Score
3.1
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More