Skip to content

Commit

Permalink
refactor: collect all isThenable/isPromise definitions (vercel#70293)
Browse files Browse the repository at this point in the history
This collects the few different isThenable and isPromise definitions
into one shared function
  • Loading branch information
wyattjoh authored Sep 20, 2024
1 parent cd33300 commit cf59073
Show file tree
Hide file tree
Showing 6 changed files with 26 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -272,15 +272,3 @@ export type ReducerActions = Readonly<
| HmrRefreshAction
| ServerActionAction
>

export function isThenable(value: any): value is Promise<AppRouterState> {
// TODO: We don't gain anything from this abstraction. It's unsound, and only
// makes sense in the specific places where we use it. So it's better to keep
// the type coercion inline, instead of leaking this to other places in
// the codebase.
return (
value &&
(typeof value === 'object' || typeof value === 'function') &&
typeof value.then === 'function'
)
}
10 changes: 5 additions & 5 deletions packages/next/src/client/components/use-reducer-with-devtools.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { Dispatch } from 'react'
import React, { use } from 'react'
import { useRef, useEffect, useCallback } from 'react'
import {
isThenable,
type AppRouterState,
type ReducerActions,
type ReducerState,
import type {
AppRouterState,
ReducerActions,
ReducerState,
} from './router-reducer/router-reducer-types'
import type { AppRouterActionQueue } from '../../shared/lib/router/action-queue'
import { isThenable } from '../../shared/lib/is-thenable'

export type ReduxDevtoolsSyncFn = (state: AppRouterState) => void

Expand Down
12 changes: 2 additions & 10 deletions packages/next/src/server/after/after-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ResponseCookies } from '../web/spec-extension/cookies'
import type { RequestLifecycleOpts } from '../base-server'
import type { AfterCallback, AfterTask } from './after'
import { InvariantError } from '../../shared/lib/invariant-error'
import { isThenable } from '../../shared/lib/is-thenable'

export type AfterContextOpts = {
waitUntil: RequestLifecycleOpts['waitUntil'] | undefined
Expand Down Expand Up @@ -36,7 +37,7 @@ export class AfterContext {
}

public after(task: AfterTask): void {
if (isPromise(task)) {
if (isThenable(task)) {
task.catch(() => {}) // avoid unhandled rejection crashes
if (!this.waitUntil) {
errorWaitUntilNotAvailable()
Expand Down Expand Up @@ -141,12 +142,3 @@ function wrapRequestStoreForAfterCallbacks(
serverComponentsHmrCache: requestStore.serverComponentsHmrCache,
}
}

function isPromise(p: unknown): p is Promise<unknown> {
return (
p !== null &&
typeof p === 'object' &&
'then' in p &&
typeof p.then === 'function'
)
}
7 changes: 2 additions & 5 deletions packages/next/src/server/lib/trace/tracer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
AttributeValue,
TextMapGetter,
} from 'next/dist/compiled/@opentelemetry/api'
import { isThenable } from '../../../shared/lib/is-thenable'

let api: typeof import('next/dist/compiled/@opentelemetry/api')

Expand All @@ -34,10 +35,6 @@ if (process.env.NEXT_RUNTIME === 'edge') {
const { context, propagation, trace, SpanStatusCode, SpanKind, ROOT_CONTEXT } =
api

const isPromise = <T>(p: any): p is Promise<T> => {
return p !== null && typeof p === 'object' && typeof p.then === 'function'
}

export class BubbledError extends Error {
constructor(
public readonly bubble?: boolean,
Expand Down Expand Up @@ -352,7 +349,7 @@ class NextTracerImpl implements NextTracer {
}

const result = fn(span)
if (isPromise(result)) {
if (isThenable(result)) {
// If there's error make sure it throws
return result
.then((res) => {
Expand Down
16 changes: 16 additions & 0 deletions packages/next/src/shared/lib/is-thenable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Check to see if a value is Thenable.
*
* @param promise the maybe-thenable value
* @returns true if the value is thenable
*/
export function isThenable<T = unknown>(
promise: Promise<T> | T
): promise is Promise<T> {
return (
promise !== null &&
typeof promise === 'object' &&
'then' in promise &&
typeof promise.then === 'function'
)
}
2 changes: 1 addition & 1 deletion packages/next/src/shared/lib/router/action-queue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {
isThenable,
type AppRouterState,
type ReducerActions,
type ReducerState,
Expand All @@ -11,6 +10,7 @@ import {
import type { ReduxDevToolsInstance } from '../../../client/components/use-reducer-with-devtools'
import { reducer } from '../../../client/components/router-reducer/router-reducer'
import { startTransition } from 'react'
import { isThenable } from '../is-thenable'

export type DispatchStatePromise = React.Dispatch<ReducerState>

Expand Down

0 comments on commit cf59073

Please sign in to comment.