Skip to content

Commit 2de6066

Browse files
Copilotfarfromrefug
andcommitted
Add Q&A documentation explaining request behavior
Agent-Logs-Url: https://github.com/nativescript-community/https/sessions/7bc451f5-53da-42f8-b904-b8680baa893e Co-authored-by: farfromrefug <655344+farfromrefug@users.noreply.github.com>
1 parent d43224a commit 2de6066

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed

docs/REQUEST_BEHAVIOR_QA.md

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
# iOS Request Behavior: Questions & Answers
2+
3+
This document answers common questions about how iOS requests work, especially regarding download timing and the new early resolution feature.
4+
5+
## Q1: Does request() wait for the full download to finish?
6+
7+
### Answer: **It depends on the `earlyResolve` option**
8+
9+
### Default Behavior (earlyResolve: false or not set)
10+
11+
**YES**, the request waits for the full download to complete before resolving:
12+
13+
```typescript
14+
// This WAITS for full download
15+
const response = await request({
16+
method: 'GET',
17+
url: 'https://example.com/large-file.zip'
18+
});
19+
// ← Download is 100% complete here
20+
// response.content.toFile() is instant (just moves file)
21+
```
22+
23+
**Timeline:**
24+
```
25+
1. await request() starts
26+
2. HTTP connection established
27+
3. Headers received
28+
4. Download: [====================] 100%
29+
5. await request() resolves ← HERE
30+
6. response.content.toFile() ← Instant file move (no wait)
31+
```
32+
33+
### With Early Resolution (earlyResolve: true)
34+
35+
**NO**, the request resolves immediately when headers arrive:
36+
37+
```typescript
38+
// This resolves IMMEDIATELY when headers arrive
39+
const response = await request({
40+
method: 'GET',
41+
url: 'https://example.com/large-file.zip',
42+
earlyResolve: true // NEW FEATURE
43+
});
44+
// ← Headers received, download still in progress!
45+
// response.content.toFile() WAITS for download to complete
46+
```
47+
48+
**Timeline:**
49+
```
50+
1. await request() starts
51+
2. HTTP connection established
52+
3. Headers received
53+
4. await request() resolves ← HERE (immediately!)
54+
5. Download continues: [========> ] 40%...
55+
6. response.content.toFile() called
56+
7. Download completes: [====================] 100%
57+
8. response.content.toFile() resolves ← File moved
58+
```
59+
60+
## Q2: When does toFile() wait for the download?
61+
62+
### Answer: **Only with earlyResolve: true**
63+
64+
### Default Behavior (earlyResolve: false)
65+
66+
`toFile()` does NOT wait because download is already complete:
67+
68+
```typescript
69+
const response = await request({
70+
method: 'GET',
71+
url: 'https://example.com/video.mp4'
72+
});
73+
// ↑ Download finished here (100% complete)
74+
75+
// toFile() just moves the file (instant, no network)
76+
await response.content.toFile('~/Videos/video.mp4');
77+
// ↑ File system operation only (milliseconds)
78+
```
79+
80+
### With Early Resolution (earlyResolve: true)
81+
82+
`toFile()` WAITS if download is not yet complete:
83+
84+
```typescript
85+
const response = await request({
86+
method: 'GET',
87+
url: 'https://example.com/video.mp4',
88+
earlyResolve: true
89+
});
90+
// ↑ Headers received, but download still in progress
91+
92+
// toFile() waits for download to complete
93+
await response.content.toFile('~/Videos/video.mp4');
94+
// ↑ Waits for: [remaining download] + [file move]
95+
```
96+
97+
## Q3: Can I cancel based on headers/status before full download?
98+
99+
### Answer: **YES, with earlyResolve: true**
100+
101+
This is the main benefit of early resolution:
102+
103+
```typescript
104+
const response = await request({
105+
method: 'GET',
106+
url: 'https://example.com/huge-file.zip',
107+
earlyResolve: true,
108+
tag: 'my-download'
109+
});
110+
111+
// Check headers immediately (download still in progress)
112+
console.log('Status:', response.statusCode);
113+
console.log('Size:', response.contentLength);
114+
console.log('Type:', response.headers['Content-Type']);
115+
116+
// Cancel if not what we want
117+
if (response.statusCode !== 200) {
118+
cancel('my-download'); // ← Cancels download immediately
119+
return;
120+
}
121+
122+
if (response.contentLength > 100 * 1024 * 1024) {
123+
cancel('my-download'); // ← Saves bandwidth!
124+
return;
125+
}
126+
127+
// Only proceed if headers are acceptable
128+
await response.content.toFile('~/Downloads/file.zip');
129+
```
130+
131+
## Q4: Is the download memory-efficient?
132+
133+
### Answer: **YES, always** (regardless of earlyResolve)
134+
135+
Both modes stream the download to a temp file on disk (not loaded into memory):
136+
137+
```typescript
138+
// Memory-efficient (streams to temp file)
139+
const response = await request({
140+
method: 'GET',
141+
url: 'https://example.com/500MB-video.mp4'
142+
});
143+
// Only ~2MB RAM used during download (not 500MB!)
144+
145+
// toFile() just moves the temp file (zero memory)
146+
await response.content.toFile('~/Videos/video.mp4');
147+
```
148+
149+
**Memory usage:**
150+
- **During download:** ~2-5MB RAM (buffer only, not full file)
151+
- **After download:** 0MB RAM (file on disk only)
152+
- **During toFile():** 0MB RAM (file move, no copy)
153+
154+
## Q5: What's the difference from Android?
155+
156+
### Android (OkHttp with ResponseBody)
157+
158+
Android naturally has "early resolution" behavior:
159+
160+
```kotlin
161+
// Resolves immediately when headers arrive
162+
val response = client.newCall(request).execute()
163+
// ↑ Headers available, body NOT consumed yet
164+
165+
// Check headers before consuming body
166+
println("Status: ${response.code}")
167+
println("Size: ${response.body?.contentLength()}")
168+
169+
if (response.code != 200) {
170+
response.close() // Don't consume body
171+
return
172+
}
173+
174+
// NOW consume body (streams to file)
175+
response.body?.writeTo(file)
176+
```
177+
178+
### iOS (New earlyResolve feature)
179+
180+
With `earlyResolve: true`, iOS behavior matches Android:
181+
182+
```typescript
183+
// Resolves immediately when headers arrive
184+
const response = await request({
185+
method: 'GET',
186+
url: '...',
187+
earlyResolve: true
188+
});
189+
// ↑ Headers available, download in background
190+
191+
// Check headers before consuming
192+
console.log('Status:', response.statusCode);
193+
console.log('Size:', response.contentLength);
194+
195+
if (response.statusCode !== 200) {
196+
cancel(tag); // Don't consume body
197+
return;
198+
}
199+
200+
// NOW consume body (waits for download, moves file)
201+
await response.content.toFile('...');
202+
```
203+
204+
## Summary Table
205+
206+
| Scenario | When request() resolves | When toFile() completes | Can cancel early? | Memory efficient? |
207+
|----------|------------------------|-------------------------|-------------------|------------------|
208+
| **Default iOS** | After full download | Immediately (file move) | ❌ No | ✅ Yes |
209+
| **iOS with earlyResolve** | After headers received | After download + file move | ✅ Yes | ✅ Yes |
210+
| **Android** | After headers received | After stream consumption | ✅ Yes | ✅ Yes |
211+
212+
## When to Use Early Resolution?
213+
214+
### ✅ Use earlyResolve: true when:
215+
216+
- Downloading large files (> 10MB)
217+
- Need to validate headers/status before proceeding
218+
- Want to cancel based on content-length or content-type
219+
- Need to show file info (size, type) to user before downloading
220+
- Building a download manager with conditional downloads
221+
222+
### ❌ Don't use earlyResolve: true when:
223+
224+
- Small API responses (< 1MB)
225+
- Always need the full content (no conditional logic)
226+
- Simple requests where you don't inspect headers
227+
- Backward compatibility is critical
228+
229+
## Code Examples
230+
231+
### Example 1: Conditional Download
232+
233+
```typescript
234+
async function conditionalDownload(url: string) {
235+
const response = await request({
236+
method: 'GET',
237+
url,
238+
earlyResolve: true,
239+
tag: url
240+
});
241+
242+
// Check if we want this file
243+
const fileSize = response.contentLength;
244+
const contentType = response.headers['Content-Type'];
245+
246+
if (fileSize > 50 * 1024 * 1024) {
247+
console.log('File too large:', fileSize);
248+
cancel(url);
249+
return null;
250+
}
251+
252+
if (!contentType?.includes('application/pdf')) {
253+
console.log('Wrong type:', contentType);
254+
cancel(url);
255+
return null;
256+
}
257+
258+
// Proceed with download
259+
return await response.content.toFile('~/Documents/file.pdf');
260+
}
261+
```
262+
263+
### Example 2: Progress with Early Feedback
264+
265+
```typescript
266+
async function downloadWithProgress(url: string) {
267+
const response = await request({
268+
method: 'GET',
269+
url,
270+
earlyResolve: true,
271+
onProgress: (current, total) => {
272+
const percent = (current / total * 100).toFixed(1);
273+
console.log(`Progress: ${percent}%`);
274+
}
275+
});
276+
277+
// Show file info immediately
278+
console.log(`Downloading ${response.contentLength} bytes`);
279+
console.log(`Type: ${response.headers['Content-Type']}`);
280+
281+
// Now wait for completion
282+
return await response.content.toFile('~/Downloads/file');
283+
}
284+
```
285+
286+
### Example 3: Multiple Format Support
287+
288+
```typescript
289+
async function smartDownload(url: string) {
290+
const response = await request({
291+
method: 'GET',
292+
url,
293+
earlyResolve: true
294+
});
295+
296+
const type = response.headers['Content-Type'] || '';
297+
298+
// Decide what to do based on content type
299+
if (type.includes('application/json')) {
300+
// Small JSON response
301+
return await response.content.toJSON();
302+
} else if (type.includes('image/')) {
303+
// Image file
304+
return await response.content.toImage();
305+
} else {
306+
// Large binary file
307+
return await response.content.toFile('~/Downloads/file');
308+
}
309+
}
310+
```
311+
312+
## Technical Details
313+
314+
### How It Works Internally
315+
316+
**Without earlyResolve:**
317+
```
318+
Alamofire DownloadRequest
319+
320+
.response(queue: .main) { response in
321+
// Fires AFTER download completes
322+
completionHandler(response, tempFilePath, error)
323+
}
324+
325+
Promise resolves with tempFilePath
326+
```
327+
328+
**With earlyResolve:**
329+
```
330+
Alamofire DownloadRequest
331+
332+
.destination { temporaryURL, response in
333+
// Fires IMMEDIATELY when headers arrive
334+
headersCallback(response, contentLength)
335+
return (tempFileURL, options)
336+
}
337+
338+
Promise resolves immediately
339+
340+
Download continues in background...
341+
342+
.response(queue: .main) { response in
343+
// Fires AFTER download completes
344+
completionHandler(response, tempFilePath, error)
345+
// Updates HttpsResponseLegacy.tempFilePath
346+
// Resolves downloadCompletionPromise
347+
}
348+
349+
toFile() completes
350+
```
351+
352+
### HttpsResponseLegacy Internals
353+
354+
```typescript
355+
class HttpsResponseLegacy {
356+
private downloadCompletionPromise?: Promise<void>;
357+
private downloadCompleted: boolean = false;
358+
359+
async toFile(path: string): Promise<File> {
360+
// Wait for download if not complete
361+
await this.waitForDownloadCompletion();
362+
363+
// Now tempFilePath is available
364+
// Move temp file to destination
365+
fileManager.moveItem(this.tempFilePath, path);
366+
}
367+
}
368+
```
369+
370+
## See Also
371+
372+
- [Early Resolution Documentation](./EARLY_RESOLUTION.md) - Full feature guide
373+
- [iOS Streaming Implementation](./IOS_STREAMING_IMPLEMENTATION.md) - Technical details
374+
- [iOS/Android Parity](./IOS_ANDROID_BEHAVIOR_PARITY.md) - Platform comparison

0 commit comments

Comments
 (0)