Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: collect all isThenable/isPromise definitions #70293

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading