Sube y descarga archivos

Usa FormData para cargas multipart y un responseType explícito para las descargas. Estas funciones están pensadas para una aplicación en el navegador; el endpoint de carga devuelve JSON { "id": "..." }.

import { Http, rest } from "fetchzen";
import type { HttpProgressHandler } from "fetchzen";
const api = Http.create("https://api.example.com", {
preset: rest,
cache: false,
});
export async function uploadFile(file: File, signal?: AbortSignal) {
const form = new FormData();
form.append("file", file, file.name);
const { data } = await api.post<FormData, { id: string }>("/files", form, {
signal,
});
return data;
}
export async function downloadFile(
id: string,
onProgress: HttpProgressHandler,
signal?: AbortSignal,
): Promise<Blob> {
const { data } = await api.get<Blob>(`/files/${encodeURIComponent(id)}`, {
responseType: "blob",
onDownloadProgress: onProgress,
signal,
});
return data;
}

No establezcas a mano el Content-Type de multipart. El cliente pasa FormData sin modificarlo y elimina ese encabezado para que fetch genere el separador de las partes. Las cargas directas de Blob/ArrayBuffer también se pasan sin modificar, pero no reemplazan automáticamente el tipo de contenido JSON predeterminado del cliente; indica el valor adecuado en headers para esas cargas.

Los cuerpos de solicitud ReadableStream dependen del entorno. El cliente agrega duplex: "half" si se omite y lo pasa a Fetch nativo o personalizado; también puedes indicarlo explícitamente. Esto no habilita cargas en streaming en navegadores que no las admiten. No ofrece progreso de carga ni cargas reanudables.

  • onDownloadProgress recibe { loaded, total?, percent?, done }. loaded cuenta bytes; total y percent dependen de un Content-Length utilizable. Un Content-Encoding distinto de identity deja ambos indefinidos porque la compresión puede hacer que la longitud declarada difiera de los bytes leídos. Una respuesta CORS sin un encabezado de codificación visible también tiene un total desconocido; un servidor de otro origen que no comprime puede enviar y exponer Content-Encoding: identity. Si no hay total, muestra un indicador indeterminado.
  • Fetch nativo no ofrece callbacks de transferencia de carga. Las cargas directas y multipart mantienen una espera indeterminada hasta su respuesta. Usa uploadConfirmed con un endpoint de fragmentos compatible para progreso confirmado; no es progreso de transporte ni división automática para un endpoint ordinario.
  • responseType: "blob" guarda toda la descarga en memoria. Si necesitas bytes, combina get<ArrayBuffer> con responseType: "arrayBuffer".
  • Para descargas grandes, usa get<ReadableStream<Uint8Array> | null> con responseType: "stream" y consume o cancela el stream devuelto. La petición se resuelve antes de terminar la lectura; también debes manejar los errores de lectura. El progreso avanza al consumir el stream, no solo al recibir la respuesta inicial.
  • Las respuestas binarias y de tipo stream no se clonan de forma predeterminada. Usa el data devuelto en lugar de intentar leer otra vez el cuerpo de la respuesta después de una descarga almacenada en memoria.
  • La envoltura de progreso y los clones conservan Response.url, redirected y type; no inventan un total conocido para respuestas comprimidas.

Para mostrar un blob descargado, crea una URL de objeto con URL.createObjectURL(blob) y libérala con URL.revokeObjectURL(url) cuando dejes de usarla.

Forzar un tipo de respuesta binario también afecta las respuestas de error. rest sigue rechazando los estados HTTP de fallo, pero un cuerpo de error JSON no se interpreta como JSON si seleccionas responseType: "blob".

Consulta también: cancelación de peticiones obsoletas. Prueba carga de archivos, descarga o streaming.