diff --git a/docs/typed/isPromise.mdx b/docs/typed/isPromise.mdx index 8afdb6e6..6d95c979 100644 --- a/docs/typed/isPromise.mdx +++ b/docs/typed/isPromise.mdx @@ -1,16 +1,28 @@ --- title: isPromise -description: 'Determine if a value is a Promise' +description: 'Determine if a value is a Promise or has a `then` method' --- ### Usage -Pass in a value and get a boolean telling you if the value is a Promise. This function is not _"bullet proof"_ because determining if a value is a Promise in javascript is not _"bullet proof"_. The standard/recommended method is to use `Promise.resolve` to essentially cast any value, promise or not, into an awaited value. However, this may do in a pinch. +The `isPromise` function checks if a value is "Promise-like" by determining if it has a `then` method. ```ts import * as _ from 'radashi' +_.isPromise({ then: () => {} }) // => true +_.isPromise(new Promise(() => {})) // => true +_.isPromise(Promise.resolve(1)) // => true +_.isPromise(Promise.reject(new Error('nope'))) // => true + _.isPromise('hello') // => false -_.isPromise(['hello']) // => false -_.isPromise(new Promise(res => res())) // => true +_.isPromise({}) // => false ``` + +This approach is useful for identifying objects that conform to the Promise interface without actually being instances of `Promise`. It's particularly helpful in scenarios where: + +1. You need to quickly check if a value is thenable without resolving it. +2. Performance is critical, and you want to avoid the overhead of `Promise.resolve`. +3. You're working with custom Promise implementations or third-party libraries that use Promise-like objects. + +While `Promise.resolve` is generally recommended for handling both Promise and non-Promise values uniformly, `isPromise` can be preferable when you need to make decisions based on whether a value is Promise-like without actually resolving or chaining it. This can be especially useful in type-checking scenarios or when implementing control flow that depends on whether a value is immediately available or needs to be awaited. diff --git a/src/async/tryit.ts b/src/async/tryit.ts index 5ebd307d..bd5c502b 100644 --- a/src/async/tryit.ts +++ b/src/async/tryit.ts @@ -1,4 +1,4 @@ -import { isPromise } from 'radashi' +import { isPromise, type Result, type ResultPromise } from 'radashi' /** * The result of a `tryit` function. @@ -18,29 +18,36 @@ import { isPromise } from 'radashi' * }) * ``` */ -export type TryitResult = Return extends Promise - ? Promise<[Error, undefined] | [undefined, Awaited]> - : [Error, undefined] | [undefined, Return] +export type TryitResult< + TReturn, + TError extends Error = Error, +> = TReturn extends PromiseLike + ? ResultPromise + : Result /** * A helper to try an async function without forking the control flow. * Returns an error-first callback-_like_ array response as `[Error, * result]` */ -export function tryit( - func: (...args: Args) => Return, -): (...args: Args) => TryitResult { - return (...args) => { +export function tryit< + TArgs extends any[], + TReturn, + TError extends Error = Error, +>( + func: (...args: TArgs) => TReturn, +): (...args: TArgs) => TryitResult { + return (...args): any => { try { const result = func(...args) - if (isPromise(result)) { - return result - .then(value => [undefined, value]) - .catch(err => [err, undefined]) as TryitResult - } - return [undefined, result] as TryitResult + return isPromise(result) + ? result.then( + value => [undefined, value], + err => [err, undefined], + ) + : [undefined, result] } catch (err) { - return [err, undefined] as TryitResult + return [err, undefined] } } } diff --git a/src/typed/isPromise.ts b/src/typed/isPromise.ts index 993002e3..663ce65a 100644 --- a/src/typed/isPromise.ts +++ b/src/typed/isPromise.ts @@ -11,6 +11,6 @@ import { isFunction } from 'radashi' * isPromise(1) // => false * ``` */ -export function isPromise(value: any): value is Promise { +export function isPromise(value: any): value is PromiseLike { return !!value && isFunction(value.then) } diff --git a/tests/typed/isPromise.test.ts b/tests/typed/isPromise.test.ts index 80173c33..9bc654f4 100644 --- a/tests/typed/isPromise.test.ts +++ b/tests/typed/isPromise.test.ts @@ -1,12 +1,14 @@ import * as _ from 'radashi' describe('isPromise', () => { - test('return true for Promise values', () => { - expect(_.isPromise(new Promise(res => res(0)))).toBeTruthy() - expect(_.isPromise(new Promise(res => res('')))).toBeTruthy() + test('return true for Promise-like values', () => { + expect(_.isPromise(new Promise(() => {}))).toBeTruthy() + expect(_.isPromise(Promise.resolve(1))).toBeTruthy() expect(_.isPromise((async () => {})())).toBeTruthy() + // biome-ignore lint/suspicious/noThenProperty: + expect(_.isPromise({ then: () => {} })).toBeTruthy() }) - test('return false for non-Date values', () => { + test('return false for non-Promise-like values', () => { expect(_.isPromise(22)).toBeFalsy() expect(_.isPromise({ name: 'x' })).toBeFalsy() expect(_.isPromise('abc')).toBeFalsy()