diff --git a/packages/edge/docs/README.md b/packages/edge/docs/README.md index 20b40766d2..666a01dc09 100644 --- a/packages/edge/docs/README.md +++ b/packages/edge/docs/README.md @@ -24,6 +24,7 @@ - [ipAddress](README.md#ipaddress) - [json](README.md#json) - [next](README.md#next) +- [potentiallyLongRunningResponse](README.md#potentiallylongrunningresponse) - [rewrite](README.md#rewrite) ## Variables @@ -261,6 +262,43 @@ export default function middleware(_req: Request) { --- +### potentiallyLongRunningResponse + +▸ **potentiallyLongRunningResponse**(`dataPromise`, `init?`): `Response` + +Builds a response for returning data based on promise that take many seconds to resolve. +The response is returned immediately, but data is only written to it when the promise resolves. + +**`Example`** + +```ts +import { potentiallyLongRunningResponse } from '@vercel/edge'; + +export default () => { + const slowPromise = new Promise(resolve => + setTimeout(() => resolve('Done'), 20000) + ); + return potentiallyLongRunningResponse(slowPromise); +}; +``` + +#### Parameters + +| Name | Type | Description | +| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dataPromise` | [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<`string` \| [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\> | Promise for data to be sent as the response body. Note, that if this promise is rejected, then a plain text "ERROR" is returned to the cliet. Catch errors on the promise yourself to add custom error handling. | +| `init?` | `ResponseInit` | optional custom response status, statusText and headers | + +#### Returns + +`Response` + +#### Defined in + +[src/response.ts:43](https://github.com/vercel/vercel/blob/main/packages/edge/src/response.ts#L43) + +--- + ### rewrite ▸ **rewrite**(`destination`, `init?`): `Response` diff --git a/packages/edge/src/response.ts b/packages/edge/src/response.ts index 57c64487a5..f57462fe1c 100644 --- a/packages/edge/src/response.ts +++ b/packages/edge/src/response.ts @@ -20,3 +20,51 @@ export function json(data: any, init?: ResponseInit): Response { // @ts-expect-error This is not in lib/dom right now, and we can't augment it. return Response.json(data, init); } + +/** + * Builds a response for returning data based on promise that take many seconds to resolve. + * The response is returned immediately, but data is only written to it when the promise resolves. + * + * @param dataPromise Promise for data to be sent as the response body. Note, that if this promise is + * rejected, then a plain text "ERROR" is returned to the cliet. Catch errors on the promise yourself + * to add custom error handling. + * @param init optional custom response status, statusText and headers + * + * @example + * ```ts + * import { potentiallyLongRunningResponse } from '@vercel/edge'; + * + * export default () => { + * const slowPromise = new Promise((resolve) => setTimeout(() => resolve("Done"), 20000)); + * return potentiallyLongRunningResponse(slowPromise); + * }; + * ``` + */ +export function potentiallyLongRunningResponse( + dataPromise: Promise, + init?: ResponseInit +): Response { + return new Response( + new ReadableStream({ + start(controller) { + dataPromise + .then((data: string | Uint8Array) => { + if (typeof data === 'string') { + controller.enqueue(new TextEncoder().encode(data)); + } else { + controller.enqueue(data); + } + controller.close(); + }) + .catch(error => { + console.log( + `Error in 'potentiallyLongRunningResponse' dataPromise: ${error}` + ); + controller.enqueue(new TextEncoder().encode('ERROR')); + controller.close(); + }); + }, + }), + init + ); +} diff --git a/packages/edge/test/response.test.ts b/packages/edge/test/response.test.ts index 1b361d8754..d73a8ff781 100644 --- a/packages/edge/test/response.test.ts +++ b/packages/edge/test/response.test.ts @@ -2,7 +2,7 @@ * @jest-environment @edge-runtime/jest-environment */ -import { json } from '../src/response'; +import { json, potentiallyLongRunningResponse } from '../src/response'; describe('json', () => { it('returns a response with JSON content', async () => { @@ -32,3 +32,50 @@ describe('json', () => { expect(await response.json()).toEqual(content); }); }); + +describe('potentiallyLongRunningResponse', () => { + it('returns a response with immediate data', async () => { + const response = potentiallyLongRunningResponse( + new Promise(resolve => resolve('test')) + ); + expect(await response.text()).toBe('test'); + }); + + it('returns a response after a timeout', async () => { + const slowPromise: Promise = new Promise(resolve => + setTimeout(() => resolve('after timeout'), 1000) + ); + const response = potentiallyLongRunningResponse(slowPromise); + expect(await response.text()).toBe('after timeout'); + }); + + it('returns a response with custom init', async () => { + const response = potentiallyLongRunningResponse( + new Promise(resolve => resolve('test')), + { + status: 400, + headers: { + 'content-type': 'text/custom', + }, + } + ); + expect(await response.text()).toBe('test'); + expect(response.status).toBe(400); + expect(response.headers.get('content-type')).toBe('text/custom'); + }); + + it('returns a response with Uint8Array data', async () => { + const data = new TextEncoder().encode('data'); + const response = potentiallyLongRunningResponse( + new Promise(resolve => resolve(data)) + ); + expect(await response.text()).toBe('data'); + }); + + it('returns ERROR on rejected promise', async () => { + const response = potentiallyLongRunningResponse( + new Promise((_, reject) => reject(new Error('test'))) + ); + expect(await response.text()).toBe('ERROR'); + }); +});