diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index f221b0e01a..28d8d273ac 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -58,7 +58,7 @@ jobs: with: run: node ./bin/pr.js - name: Create pull request - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 id: cpr with: branch: release diff --git a/.github/workflows/sponsors.yml b/.github/workflows/sponsors.yml index 5df13dcbaa..54cd872bd5 100644 --- a/.github/workflows/sponsors.yml +++ b/.github/workflows/sponsors.yml @@ -44,7 +44,7 @@ jobs: echo "$CONTENT" - name: Create pull request if: ${{ steps.sponsors.outputs.changed == 'true'}} - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 id: cpr with: branch: sponsors diff --git a/ECOSYSTEM.md b/ECOSYSTEM.md index b0257fe330..35244575ee 100644 --- a/ECOSYSTEM.md +++ b/ECOSYSTEM.md @@ -16,10 +16,14 @@ This is a list of axios related libraries and resources. If you have a suggestio * [axios-api-versioning](https://weffe.github.io/axios-api-versioning) - Add easy to manage api versioning to axios * [axios-data-unpacker](https://github.com/anubhavsrivastava/axios-data-unpacker) - Axios interceptor that unpacks HTTP responses so that you can focus on actual server data. * [r2curl](https://github.com/uyu423/r2curl) - Extracts the cURL command string from the Axios object. (AxiosResponse, AxiosRequestConfig) -* [swagger-taxos-codegen](https://github.com/michalzaq12/swagger-taxos-codegen) - Axios based Swagger Codegen (tailored for typescript) * [axios-endpoints](https://github.com/renancaraujo/axios-endpoints) - Axios endpoints help you to create a more concise endpoint mapping with axios. * [axios-multi-api](https://github.com/MattCCC/axios-multi-api) - Easy API handling whenever there are many endpoints to add. It helps to make Axios requests in an easy and declarative manner. * [axios-url-template](https://github.com/rafw87/axios-url-template) - Axios interceptor adding support for URL templates. + +### API clients + +* [@hey-api/openapi-ts](https://heyapi.dev/openapi-ts/clients/axios) - The OpenAPI to TypeScript codegen. Generate clients, SDKs, validators, and more. +* [swagger-taxos-codegen](https://github.com/michalzaq12/swagger-taxos-codegen) - Axios based Swagger Codegen (tailored for typescript) * [zodios](https://www.zodios.org) - Typesafe API client based on axios ### Logging and debugging diff --git a/README.md b/README.md index a0c09ec5a3..a915d51fb1 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ Route4Me

Best Route Planning And Route Optimization Software

Explore | Free Trial | Contact

Buzzoid - Buy Instagram Followers

At Buzzoid, you can buy Instagram followers quickly, safely, and easily with just a few clicks. Rate...

buzzoid.com

Poprey - Buy Instagram Likes

Buy Instagram Likes

poprey.com

- Requestly

A lightweight open-source API Development, Testing & Mocking platform

requestly.com

- 💜 Become a sponsor + Букмекер

Ставки на спорт, БК в Україні

betking.com.ua

+ Requestly

A lightweight open-source API Development, Testing & Mocking platform

requestly.com

💜 Become a sponsor @@ -65,6 +65,7 @@ - [Interceptors](#interceptors) - [Multiple Interceptors](#multiple-interceptors) - [Handling Errors](#handling-errors) + - [Handling Timeouts](#handling-timeouts) - [Cancellation](#cancellation) - [AbortController](#abortcontroller) - [CancelToken 👎](#canceltoken-deprecated) @@ -188,13 +189,13 @@ const axios = require('axios/dist/browser/axios.cjs'); // browser commonJS bundl Using jsDelivr CDN (ES5 UMD browser module): ```html - + ``` Using unpkg CDN: ```html - + ``` ## Example @@ -921,6 +922,27 @@ axios.get('/user/12345') }); ``` +## Handling Timeouts + +```js +async function fetchWithTimeout() { + try { + const response = await axios.get('https://example.com/data', { + timeout: 5000 // 5 seconds + }); + + console.log('Response:', response.data); + + } catch (error) { + if (axios.isAxiosError(error) && error.code === 'ECONNABORTED') { + console.error('❌ Request timed out!'); + } else { + console.error('❌ Error:', error.message); + } + } +} +``` + ## Cancellation ### AbortController @@ -1082,7 +1104,7 @@ The server will handle it as: If your backend body-parser (like `body-parser` of `express.js`) supports nested objects decoding, you will get the same object on the server-side automatically ```js - var app = express(); + const app = express(); app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies @@ -1098,7 +1120,7 @@ If your backend body-parser (like `body-parser` of `express.js`) supports nested ### FormData -To send the data as a `multipart/formdata` you need to pass a formData instance as a payload. +To send the data as a `multipart/form-data` you need to pass a formData instance as a payload. Setting the `Content-Type` header is not required as Axios guesses it based on the payload type. ```js @@ -1778,6 +1800,25 @@ If use ESM, your settings should be fine. If you compile TypeScript to CJS and you can’t use `"moduleResolution": "node 16"`, you have to enable `esModuleInterop`. If you use TypeScript to type check CJS JavaScript code, your only option is to use `"moduleResolution": "node16"`. + +You can also create a custom instance with typed interceptors: + +```typescript +import axios, { AxiosInstance, InternalAxiosRequestConfig } from 'axios'; + +const apiClient: AxiosInstance = axios.create({ + baseURL: 'https://api.example.com', + timeout: 10000, +}); + +apiClient.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + // Add auth token + return config; + } +); +``` + ## Online one-click setup You can use Gitpod, an online IDE(which is free for Open Source) for contributing or running the examples online. diff --git a/bin/actions/notify_published.js b/bin/actions/notify_published.js index ddacdec31d..22a50d9432 100644 --- a/bin/actions/notify_published.js +++ b/bin/actions/notify_published.js @@ -5,11 +5,11 @@ import fs from 'fs/promises'; const argv = minimist(process.argv.slice(2)); console.log(argv); -let {tag} = argv; +let { tag } = argv; -(async() => { +(async () => { if (!tag || tag === true) { - const {version} = JSON.parse((await fs.readFile('./package.json')).toString()); + const { version } = JSON.parse((await fs.readFile('./package.json')).toString()); tag = 'v' + version; } else if (typeof tag !== 'string') { diff --git a/examples/abort-controller/index.html b/examples/abort-controller/index.html new file mode 100644 index 0000000000..bd3747ae65 --- /dev/null +++ b/examples/abort-controller/index.html @@ -0,0 +1,132 @@ + + + + axios - abort controller example + + + + +

axios.AbortController

+ +
+
+

1. Single Request Cancellation

+

Click "Start Request" to begin a 3-second request. Click "Cancel Request" to abort it.

+ + +
+
+
+ +
+ +
+
+

2. Search-as-you-type (Race Condition Handling)

+

Type quickly. Previous pending requests will be cancelled automatically.

+
+ +
+
+ +
+
+ + + + + diff --git a/examples/abort-controller/server.js b/examples/abort-controller/server.js new file mode 100644 index 0000000000..8e27326576 --- /dev/null +++ b/examples/abort-controller/server.js @@ -0,0 +1,16 @@ +import url from 'url'; + +export default function (req, res) { + const parsedUrl = url.parse(req.url, true); + const delay = parsedUrl.query.delay || 3000; + + setTimeout(() => { + res.writeHead(200, { + 'Content-Type': 'text/json' + }); + res.write(JSON.stringify({ + message: 'Response completed successfully after ' + delay + 'ms' + })); + res.end(); + }, delay); +}; diff --git a/examples/get/server.js b/examples/get/server.js index 8a3622f5c7..7312f42b8d 100644 --- a/examples/get/server.js +++ b/examples/get/server.js @@ -27,7 +27,7 @@ const people = [ export default function (req, res) { res.writeHead(200, { - 'Content-Type': 'text/json' + 'Content-Type': 'application/json' }); res.write(JSON.stringify(people)); res.end(); diff --git a/examples/post/server.js b/examples/post/server.js index 1cb9c45fa6..68756d1b8d 100644 --- a/examples/post/server.js +++ b/examples/post/server.js @@ -8,7 +8,7 @@ export default function (req, res) { req.on('end', function () { console.log('POST data received'); res.writeHead(200, { - 'Content-Type': 'text/json' + 'Content-Type': 'application/json' }); res.write(JSON.stringify(data)); res.end(); diff --git a/examples/postMultipartFormData/server.js b/examples/postMultipartFormData/server.js index 9db463ea2c..eb95af6a34 100644 --- a/examples/postMultipartFormData/server.js +++ b/examples/postMultipartFormData/server.js @@ -6,7 +6,7 @@ export default function (req, res) { req.on('end', function () { console.log('POST received'); res.writeHead(200, { - 'Content-Type': 'text/json' + 'Content-Type': 'application/json' }); res.end(); }); diff --git a/index.d.cts b/index.d.cts index e3a06a3789..4be32f9cb3 100644 --- a/index.d.cts +++ b/index.d.cts @@ -515,14 +515,32 @@ declare namespace axios { runWhen?: (config: InternalAxiosRequestConfig) => boolean; } - type AxiosRequestInterceptorUse = (onFulfilled?: ((value: T) => T | Promise) | null, onRejected?: ((error: any) => any) | null, options?: AxiosInterceptorOptions) => number; + type AxiosInterceptorFulfilled = (value: T) => T | Promise; + type AxiosInterceptorRejected = (error: any) => any; - type AxiosResponseInterceptorUse = (onFulfilled?: ((value: T) => T | Promise) | null, onRejected?: ((error: any) => any) | null) => number; + type AxiosRequestInterceptorUse = ( + onFulfilled?: AxiosInterceptorFulfilled | null, + onRejected?: AxiosInterceptorRejected | null, + options?: AxiosInterceptorOptions + ) => number; + + type AxiosResponseInterceptorUse = ( + onFulfilled?: AxiosInterceptorFulfilled | null, + onRejected?: AxiosInterceptorRejected | null + ) => number; + + interface AxiosInterceptorHandler { + fulfilled: AxiosInterceptorFulfilled; + rejected?: AxiosInterceptorRejected; + synchronous: boolean; + runWhen?: (config: AxiosRequestConfig) => boolean; + } interface AxiosInterceptorManager { use: V extends AxiosResponse ? AxiosResponseInterceptorUse : AxiosRequestInterceptorUse; eject(id: number): void; clear(): void; + handlers?: Array>; } interface AxiosInstance extends Axios { diff --git a/index.d.ts b/index.d.ts index ff29215f5c..c562d9c08e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -494,14 +494,32 @@ export interface AxiosInterceptorOptions { runWhen?: (config: InternalAxiosRequestConfig) => boolean; } -type AxiosRequestInterceptorUse = (onFulfilled?: ((value: T) => T | Promise) | null, onRejected?: ((error: any) => any) | null, options?: AxiosInterceptorOptions) => number; +type AxiosInterceptorFulfilled = (value: T) => T | Promise; +type AxiosInterceptorRejected = (error: any) => any; -type AxiosResponseInterceptorUse = (onFulfilled?: ((value: T) => T | Promise) | null, onRejected?: ((error: any) => any) | null) => number; +type AxiosRequestInterceptorUse = ( + onFulfilled?: AxiosInterceptorFulfilled | null, + onRejected?: AxiosInterceptorRejected | null, + options?: AxiosInterceptorOptions +) => number; + +type AxiosResponseInterceptorUse = ( + onFulfilled?: AxiosInterceptorFulfilled | null, + onRejected?: AxiosInterceptorRejected | null +) => number; + +interface AxiosInterceptorHandler { + fulfilled: AxiosInterceptorFulfilled; + rejected?: AxiosInterceptorRejected; + synchronous: boolean; + runWhen: (config: AxiosRequestConfig) => boolean | null; +} export interface AxiosInterceptorManager { use: V extends AxiosResponse ? AxiosResponseInterceptorUse : AxiosRequestInterceptorUse; eject(id: number): void; clear(): void; + handlers?: Array>; } export class Axios { diff --git a/lib/adapters/http.js b/lib/adapters/http.js index dfa6386d32..6a8a05dd2a 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -268,7 +268,8 @@ const buildAddressEntry = (address, family) => resolveFamily(utils.isObject(addr const http2Transport = { request(options, cb) { - const authority = options.protocol + '//' + options.hostname + ':' + (options.port || 80); + const authority = options.protocol + '//' + options.hostname + ':' + (options.port ||(options.protocol === 'https:' ? 443 : 80)); + const {http2Options, headers} = options; diff --git a/lib/core/mergeConfig.js b/lib/core/mergeConfig.js index b1b50f0fc9..b7a43d5207 100644 --- a/lib/core/mergeConfig.js +++ b/lib/core/mergeConfig.js @@ -21,7 +21,7 @@ export default function mergeConfig(config1, config2) { function getMergedValue(target, source, prop, caseless) { if (utils.isPlainObject(target) && utils.isPlainObject(source)) { - return utils.merge.call({caseless}, target, source); + return utils.merge.call({ caseless }, target, source); } else if (utils.isPlainObject(source)) { return utils.merge({}, source); } else if (utils.isArray(source)) { @@ -30,7 +30,6 @@ export default function mergeConfig(config1, config2) { return source; } - // eslint-disable-next-line consistent-return function mergeDeepProperties(a, b, prop, caseless) { if (!utils.isUndefined(b)) { return getMergedValue(a, b, prop, caseless); @@ -96,7 +95,7 @@ export default function mergeConfig(config1, config2) { headers: (a, b, prop) => mergeDeepProperties(headersToObject(a), headersToObject(b), prop, true) }; - utils.forEach(Object.keys({...config1, ...config2}), function computeConfigValue(prop) { + utils.forEach(Object.keys({ ...config1, ...config2 }), function computeConfigValue(prop) { const merge = mergeMap[prop] || mergeDeepProperties; const configValue = merge(config1[prop], config2[prop], prop); (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue); diff --git a/lib/helpers/composeSignals.js b/lib/helpers/composeSignals.js index 84087c8067..cf27ddf5e3 100644 --- a/lib/helpers/composeSignals.js +++ b/lib/helpers/composeSignals.js @@ -21,7 +21,7 @@ const composeSignals = (signals, timeout) => { let timer = timeout && setTimeout(() => { timer = null; - onabort(new AxiosError(`timeout ${timeout} of ms exceeded`, AxiosError.ETIMEDOUT)) + onabort(new AxiosError(`timeout of ${timeout}ms exceeded`, AxiosError.ETIMEDOUT)) }, timeout) const unsubscribe = () => { diff --git a/lib/helpers/spread.js b/lib/helpers/spread.js index 13479cb25c..2e72fc88f2 100644 --- a/lib/helpers/spread.js +++ b/lib/helpers/spread.js @@ -7,7 +7,7 @@ * * ```js * function f(x, y, z) {} - * var args = [1, 2, 3]; + * const args = [1, 2, 3]; * f.apply(null, args); * ``` * diff --git a/lib/utils.js b/lib/utils.js index ed081611d1..68bab356c4 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -333,7 +333,7 @@ const isContextDefined = (context) => !isUndefined(context) && context !== _glob * Example: * * ```js - * var result = merge({foo: 123}, {foo: 456}); + * const result = merge({foo: 123}, {foo: 456}); * console.log(result.foo); // outputs 456 * ``` * diff --git a/package-lock.json b/package-lock.json index d5604b1149..a3086512ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11782,9 +11782,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -11861,9 +11861,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -36255,9 +36255,9 @@ } }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==" }, "for-in": { "version": "1.0.2", @@ -36299,9 +36299,9 @@ "dev": true }, "form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", diff --git a/package.json b/package.json index f0973efed4..eb58f1a251 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "name": "axios", "version": "1.13.2", "description": "Promise based HTTP client for the browser and node.js", - "main": "index.js", + "main": "./dist/node/axios.cjs", + "module": "./index.js", "exports": { ".": { "types": { diff --git a/test/unit/helpers/composeSignals.js b/test/unit/helpers/composeSignals.js index 961921e4ab..89cfa11abb 100644 --- a/test/unit/helpers/composeSignals.js +++ b/test/unit/helpers/composeSignals.js @@ -32,7 +32,7 @@ describe('helpers::composeSignals', () => { signal.addEventListener('abort', resolve); }); - assert.match(String(signal.reason), /timeout 100 of ms exceeded/); + assert.match(String(signal.reason), /timeout of 100ms exceeded/); }); it('should return undefined if signals and timeout are not provided', async () => {