Skip to content

Commit 8e7877c

Browse files
authored
Merge pull request #513 from emberjs/rfc-1065
Implement functionally for "deprecate fetch" RFC
2 parents 7bf3356 + b078691 commit 8e7877c

File tree

4 files changed

+212
-1
lines changed

4 files changed

+212
-1
lines changed

addon/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export {
2020
export { default as buildWaiter, _resetWaiterNames } from './build-waiter.ts';
2121
export { default as waitForPromise } from './wait-for-promise.ts';
2222
export { default as waitFor } from './wait-for.ts';
23+
export { waitForFetch } from './wait-for-fetch.ts';

addon/src/wait-for-fetch.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { default as waitForPromise } from './wait-for-promise.ts';
2+
3+
export async function waitForFetch(fetchPromise: ReturnType<typeof fetch>) {
4+
const response = await waitForPromise(fetchPromise);
5+
6+
return new Proxy(response, {
7+
get(target, prop, receiver) {
8+
const original = Reflect.get(target, prop, receiver);
9+
10+
if (
11+
typeof prop === 'string' &&
12+
['json', 'text', 'arrayBuffer', 'blob', 'formData', 'bytes'].includes(prop)
13+
) {
14+
return (...args: unknown[]) => {
15+
return waitForPromise(original.call(target, ...args));
16+
};
17+
}
18+
19+
return original;
20+
},
21+
});
22+
}

test-apps/base-tests/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"webpack": "^5.96.1"
9292
},
9393
"engines": {
94-
"node": "16.* || >= 18"
94+
"node": ">= 18"
9595
},
9696
"ember": {
9797
"edition": "octane"
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import { module, test } from 'qunit';
2+
import { settled } from '@ember/test-helpers';
3+
import { waitForFetch, getPendingWaiterState } from '@ember/test-waiters';
4+
5+
function expectWaiters(assert: Assert, expected: number) {
6+
const result = getPendingWaiterState().pending;
7+
assert.strictEqual(
8+
result,
9+
expected,
10+
`Expecting ${expected} pending waiter(s)`,
11+
);
12+
}
13+
14+
function createFormData(obj: any) {
15+
const data = new FormData();
16+
17+
for (const [k, v] of Object.entries(obj)) {
18+
// SAFETY: just for testing, we can be a bit looser
19+
data.append(k, v as string);
20+
}
21+
22+
return data;
23+
}
24+
25+
const supported = [
26+
{
27+
method: 'json',
28+
input: new Response('{ "hi": "there" }'),
29+
parsed: { hi: 'there' },
30+
},
31+
{ method: 'text', input: new Response('text'), parsed: 'text' },
32+
{
33+
method: 'arrayBuffer',
34+
input: new Response(new ArrayBuffer(8)),
35+
parsed: new ArrayBuffer(8),
36+
},
37+
{
38+
method: 'blob',
39+
input: new Response(new Blob(['hi'])),
40+
parsed: new Blob(['hi']),
41+
},
42+
{
43+
method: 'formData',
44+
input: new Response(createFormData({ x: 1 })),
45+
parsed: createFormData({ x: 1 }),
46+
},
47+
{
48+
method: 'bytes',
49+
input: new Response(new Uint8Array([92])),
50+
parsed: new Uint8Array([92]),
51+
},
52+
] as const;
53+
54+
module('waitForFetch', function () {
55+
for (const scenario of supported) {
56+
test(`[${scenario.method}] minimal`, async function (assert) {
57+
const fetchish = new Promise<Response>((resolve) =>
58+
resolve(scenario.input),
59+
);
60+
const requestPromise = waitForFetch(fetchish);
61+
62+
expectWaiters(assert, 1);
63+
const response = await requestPromise;
64+
65+
expectWaiters(assert, 0);
66+
67+
const responsePromise = (response as any)[scenario.method]();
68+
expectWaiters(assert, 1);
69+
70+
const verify = await responsePromise;
71+
expectWaiters(assert, 0);
72+
73+
assert.deepEqual(
74+
verify,
75+
scenario.parsed,
76+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
77+
`Expected: ${scenario.parsed.toString()}`,
78+
);
79+
});
80+
}
81+
82+
test('timing: empty response', async function (assert) {
83+
function stepPromise<T>(
84+
assert: Assert,
85+
label: string,
86+
promise: Promise<T>,
87+
) {
88+
return promise
89+
.then((result) => {
90+
assert.step(`${label}:then`);
91+
return result;
92+
})
93+
.catch((e) => {
94+
assert.step(`${label}:catch`);
95+
return e;
96+
});
97+
}
98+
99+
function fetchLike(assert: Assert) {
100+
const handlers = {} as {
101+
resolve: <T>(r: T) => unknown;
102+
reject: (e: unknown) => unknown;
103+
};
104+
const original = new Promise((resolve, reject) => {
105+
assert.step('fetch:start');
106+
handlers.resolve = resolve;
107+
handlers.reject = reject;
108+
});
109+
110+
const promise = stepPromise(assert, 'fetch', original);
111+
112+
return {
113+
promise,
114+
resolve(value: unknown) {
115+
return handlers.resolve(value);
116+
},
117+
reject(e: unknown) {
118+
return handlers.reject(e);
119+
},
120+
};
121+
}
122+
123+
function proxiedResponse(assert: Assert, response: Response) {
124+
return new Proxy(response, {
125+
get(target, prop, receiver) {
126+
const original = Reflect.get(target, prop, receiver);
127+
128+
if (
129+
typeof prop === 'string' &&
130+
[
131+
'json',
132+
'text',
133+
'arrayBuffer',
134+
'blob',
135+
'formData',
136+
'bytes',
137+
].includes(prop)
138+
) {
139+
return (...args: unknown[]) => {
140+
const label = `response:${prop}`;
141+
// not part of waiting, but this tells us
142+
// we proxied correctly
143+
assert.step(label);
144+
const promise = original.call(target, ...args);
145+
146+
return stepPromise(assert, label, promise);
147+
};
148+
}
149+
150+
return original;
151+
},
152+
});
153+
}
154+
155+
const f = fetchLike(assert);
156+
157+
expectWaiters(assert, 0);
158+
const requestPromise = waitForFetch(f.promise);
159+
160+
assert.verifySteps(['fetch:start']);
161+
expectWaiters(assert, 1);
162+
163+
f.resolve(proxiedResponse(assert, new Response()));
164+
assert.verifySteps([]);
165+
expectWaiters(assert, 1);
166+
167+
await settled();
168+
assert.verifySteps(['fetch:then'], `Nothing on the response accessed`);
169+
assert.verifySteps([]);
170+
expectWaiters(assert, 0);
171+
172+
const response = await requestPromise;
173+
assert.verifySteps([]);
174+
expectWaiters(assert, 0);
175+
176+
const responsePromise = response.text();
177+
assert.verifySteps(['response:text']);
178+
expectWaiters(assert, 1);
179+
180+
await settled();
181+
assert.verifySteps(['response:text:then']);
182+
expectWaiters(assert, 0);
183+
184+
await responsePromise;
185+
assert.verifySteps([]);
186+
expectWaiters(assert, 0);
187+
});
188+
});

0 commit comments

Comments
 (0)