Fix unhandled promise rejections in rawQuery

This commit is contained in:
Konstantin Wohlwend 2025-04-11 09:59:45 -07:00
parent b493a89130
commit 4b9c7fe0ef
4 changed files with 23 additions and 16 deletions

View File

@ -26,7 +26,7 @@ export function ensurePolyfilled() {
process.on("unhandledRejection", (reason, promise) => {
captureError("unhandled-promise-rejection", reason);
if (getNodeEnvironment() === "development") {
console.error("\x1b[41mUnhandled promise rejection. Some production environments will kill the server in this case, so the server will now exit. Please use the `ignoreUnhandledRejection` function to signal that you've handled the error.\x1b[0m", reason);
console.error("\x1b[41mUnhandled promise rejection. Some production environments (particularly Vercel) will kill the server in this case, so the server will now exit. Please use the `ignoreUnhandledRejection` function to signal that you've handled the error.\x1b[0m", reason);
}
process.exit(1);
});

View File

@ -2,7 +2,9 @@ import { Prisma, PrismaClient } from "@prisma/client";
import { withAccelerate } from "@prisma/extension-accelerate";
import { getEnvVariable, getNodeEnvironment } from '@stackframe/stack-shared/dist/utils/env';
import { deepPlainEquals, filterUndefined, typedFromEntries, typedKeys } from "@stackframe/stack-shared/dist/utils/objects";
import { ignoreUnhandledRejection } from "@stackframe/stack-shared/dist/utils/promises";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { isPromise } from "util/types";
import { traceSpan } from "./utils/telemetry";
// In dev mode, fast refresh causes us to recreate many Prisma clients, eventually overloading the database.
@ -130,8 +132,15 @@ async function rawQueryArray<Q extends RawQuery<any>[]>(queries: Q): Promise<[]
const index = +type.slice(1);
unprocessed[index].push(row.json);
}
const postProcessed = queries.map((q, index) => q.postProcess(unprocessed[index]));
return postProcessed as any;
const postProcessed = queries.map((q, index) => {
const postProcessed = q.postProcess(unprocessed[index]);
// If the postProcess is async, postProcessed is a Promise. If that Promise is rejected, it will cause an unhandled promise rejection.
// We don't want that, because Vercel crashes on unhandled promise rejections.
if (isPromise(postProcessed)) {
ignoreUnhandledRejection(postProcessed);
}
});
return postProcessed;
});
}

View File

@ -26,7 +26,7 @@ export function ensurePolyfilled() {
process.on("unhandledRejection", (reason, promise) => {
captureError("unhandled-promise-rejection", reason);
if (getNodeEnvironment() === "development") {
console.error("Unhandled promise rejection. Some production environments will kill the server in this case, so the server will now exit. Please use the `ignoreUnhandledRejection` function to signal that you've handled the error.", reason);
console.error("Unhandled promise rejection. Some production environments (particularly Vercel) will kill the server in this case, so the server will now exit. Please use the `ignoreUnhandledRejection` function to signal that you've handled the error.", reason);
}
if ((globalThis.process as any).exit) {
globalThis.process.exit(1);

View File

@ -132,7 +132,9 @@ export function rejected<T>(reason: unknown): ReactPromise<T> {
return rejectedCache.get([reason]) as ReactPromise<T>;
}
const res = Object.assign(ignoreUnhandledRejection(Promise.reject(reason)), {
const promise = Promise.reject(reason);
ignoreUnhandledRejection(promise);
const res = Object.assign(promise, {
status: "rejected",
reason: reason,
} as const);
@ -223,26 +225,22 @@ import.meta.vitest?.test("pending", async ({ expect }) => {
*
* Vercel kills serverless functions on unhandled promise rejection errors, so this is important.
*/
export function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): T {
export function ignoreUnhandledRejection<T extends Promise<any>>(promise: T): void {
promise.catch(() => {});
return promise;
}
import.meta.vitest?.test("ignoreUnhandledRejection", async ({ expect }) => {
// Test with a promise that resolves
const resolvePromise = Promise.resolve(42);
const ignoredResolvePromise = ignoreUnhandledRejection(resolvePromise);
expect(ignoredResolvePromise).toBe(resolvePromise); // Should return the same promise
expect(await ignoredResolvePromise).toBe(42); // Should still resolve to the same value
ignoreUnhandledRejection(resolvePromise);
expect(await resolvePromise).toBe(42); // Should still resolve to the same value
// Test with a promise that rejects
const error = new Error("Test error");
const rejectPromise = Promise.reject(error);
const ignoredRejectPromise = ignoreUnhandledRejection(rejectPromise);
expect(ignoredRejectPromise).toBe(rejectPromise); // Should return the same promise
// The promise should still reject, but the rejection is caught internally
// so it doesn't cause an unhandled rejection error
await expect(ignoredRejectPromise).rejects.toBe(error);
const error = new Error("Test error");
const rejectPromise = Promise.reject(error);
ignoreUnhandledRejection(rejectPromise);
await expect(rejectPromise).rejects.toBe(error);
});
export async function wait(ms: number) {