نسخه اصلی این مطلب (انگلیسی): mohamadgarmabi.github.io/tanstack-fetch/blog/tanstack-fetch-2-1-0
نسخه 2.1.0 کتابخانه tanstack-fetch یک نسخه با محوریت رفع باگ است و هیچ تغییر ناسازگاری (breaking change) ندارد. با ارتقا اینها را میگیرید:
لغو یعنی لغو: اگر درخواست در حین تأخیرِ بین تلاشها (retry delay) لغو شود، بلافاصله reject میشود.
دونقطه در مسیر: مسیری مثل /models/gemini:generateContent دیگر پارامتری به اسم generateContent نمیخواهد.
فقط یک رفرش برای هر چرخش توکن: یک 401 دیررس دیگر رفرش دوم را شروع نمیکند.
مقادیر آرایهای در query، گزینههای parseAs: 'arrayBuffer' | 'stream' و entry جدید tanstack-fetch/full.
npm install [email protected]
وقتی سرور Retry-After: 5 برمیگرداند (یا هر backoff دیگری)، درخواست HTTP حتی بعد از abort شدن signal کل زمان تأخیر را صبر میکرد. TanStack Query موقع unmount و تغییر queryKey درخواست را abort میکند، پس query چند ثانیه در حالت pending میماند. حالا تأخیر به signal گوش میدهد و بلافاصله با AbortError رد میشود و تلاش بعدی هم ارسال نمیشود.
useQuery({ queryKey: ['report', id], // unmount during a 5s Retry-After → rejects now, no second fetch queryFn: ({ signal }) => api.get('/reports/:id', { params: { id }, signal }), })
حالا :name فقط وقتی در ابتدای یک بخش از مسیر بیاید پارامتر مسیر حساب میشود؛ هم در زمان اجرا و هم در تایپها:
await api.post('/v1/models/gemini:generateContent', { body }) // literal path, no params await api.get('/users/:id', { params: { id: 1 } }) // → /users/1
قبلاً فراخوانی اول با خطای missing path param "generateContent" شکست میخورد و TypeScript هم همین پارامتر را میخواست.
interceptor از قبل یک رفرش در جریان را بین چند 401 موازی به اشتراک میگذاشت. اما درخواستی که با توکن قدیمی ارسال شده بود و 401اش بعد از تمام شدن رفرش میرسید، یک رفرش جدید شروع میکرد. با refresh tokenهای چرخشی (rotating)، این فراخوانی دوم میتواند نشستی را که اولی تازه ساخته باطل کند.
حالا interceptor میداند هر درخواست بعد از کدام رفرش ارسال شده است. یک 401 دیررس فقط درخواست را با توکن جدید دوباره میفرستد:
A ──(old token)──────────────────────► 401 → retry with new token ✓ B ──(old token)──► 401 → refresh() ✓ → retry ✓ refresh() called once
حالت برعکس هم درست شده است. درخواستی که به دلیل دیگری retry شده بود (مثلاً یک 502 از طریق retry-idempotent) و بعد 401 میگرفت، رفرش را رد میکرد، چون شرط بررسی attempt > 0 بود. حالا هر درخواست حداکثر یک بار رفرش میکند و این به ازای هر فراخوانی ردیابی میشود، پس چرخه 401 → refresh → 401 همچنان نمیتواند حلقه بسازد.
این قابلیت با یک context.meta.state جدید کار میکند؛ یک object ساده که بین همه تلاشهای یک درخواست مشترک است. interceptorهای خودتان هم میتوانند از آن برای flagهای مخصوص هر فراخوانی استفاده کنند.
بدنه از نوع ReadableStream در تلاش اول مصرف میشود، بنابراین retry قبلاً با یک TypeError گیجکننده درباره «locked stream» شکست میخورد. حالا درخواستهایی که بدنه stream دارند، به جای retry، خطای واقعی (مثلاً FetchError با وضعیت 503) را برمیگردانند.
await api.get('/users', { query: { ids: [1, 2], role: 'admin' } }) // → /users?ids=1&ids=2&role=admin
QueryParams حالا آرایهای از string | number | boolean را میپذیرد و هر آیتم به صورت یک key=value جدا اضافه میشود. مقادیر null و undefined همچنان نادیده گرفته میشوند.
const bytes = await api.get<ArrayBuffer>('/files/:id', { params: { id }, parseAs: 'arrayBuffer' }) const body = await api.get<ReadableStream<Uint8Array>>('/export', { parseAs: 'stream' })
حالت 'stream' خودِ response.body را بدون خواندن برمیگرداند.
کلاینت کامل HTTP (میانبرهای option، تابع upload() و رویدادهای progress) قبلاً فقط زیر tanstack-fetch/sse بود که اگر از SSE استفاده نمیکنید عجیب به نظر میرسد. tanstack-fetch/full همان build با یک اسم واضحتر است و /sse هم مثل قبل کار میکند.
import { createFetch } from 'tanstack-fetch/full'
حالا require('tanstack-fetch') به جای .d.ts نسخه ESM، به declarationهای .d.cts resolve میشود؛ بنابراین پروژههای TypeScript که با module: node16 یا nodenext و CJS کار میکنند تایپهای درست را میگیرند. CI هم حالا همه entryها را با publint و @arethetypeswrong/cli بررسی میکند و تستها را روی Node 20، 22 و 24 اجرا میکند.
tanstack-fetch: از 3.57KB به 3.68KB
tanstack-fetch/sse: از 6.33KB به 6.43KB
tanstack-fetch/live: از 8.75KB به 8.84KB
tanstack-fetch/plugins: از 1.13KB به 1.22KB
یعنی حدود ۱۰۰ بایت برای همه این اصلاحات.
timeoutها و abortها با TimeoutError / AbortError خودِ پلتفرم reject میشوند، نه با FetchError. این همیشه همینطور بوده و حالا در README هم ذکر شده است. برای بررسیشان از isAbortError(error) استفاده کنید. (مستندات Errors)
بیشتر بخوانید: آموزش refresh token · گزینههای درخواست · Changelog · مخزن GitHub