Skip to content

Commit 41b26a4

Browse files
chore(classic): bin p-retry (#9606)
1 parent bd990d5 commit 41b26a4

File tree

10 files changed

+618
-20
lines changed

10 files changed

+618
-20
lines changed

.changeset/orange-dragons-write.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@langchain/classic": patch
3+
---
4+
5+
bin p-retry

libs/langchain-classic/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@
242242
"js-yaml": "^4.1.1",
243243
"jsonpointer": "^5.0.1",
244244
"openapi-types": "^12.1.3",
245-
"p-retry": "^7.0.0",
246245
"uuid": "^10.0.0",
247246
"yaml": "^2.2.1",
248247
"zod": "^3.25.76 || ^4"

libs/langchain-classic/src/util/hub.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import pRetry from "p-retry";
2-
1+
import pRetry from "./p-retry/index.js";
32
import { getEnvironmentVariable } from "@langchain/core/utils/env";
43
import { FileLoader, LoadValues } from "./load.js";
54
import { extname } from "./extname.js";
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
MIT License
2+
3+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
Check if an error is a [Fetch network error](https://developer.mozilla.org/en-US/docs/Web/API/fetch#exceptions)
3+
4+
@return Returns `true` if the given value is a Fetch network error, otherwise `false`.
5+
6+
@example
7+
```
8+
import isNetworkError from 'is-network-error';
9+
10+
async function getUnicorns() {
11+
try {
12+
const response = await fetch('unicorns.json');
13+
return await response.json();
14+
} catch (error) {
15+
if (isNetworkError(error)) {
16+
return localStorage.getItem('…');
17+
}
18+
19+
throw error;
20+
}
21+
}
22+
23+
console.log(await getUnicorns());
24+
```
25+
*/
26+
export default function isNetworkError(value: unknown): value is TypeError;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/* eslint-disable */
2+
const objectToString = Object.prototype.toString;
3+
4+
const isError = (value) => objectToString.call(value) === "[object Error]";
5+
6+
const errorMessages = new Set([
7+
"network error", // Chrome
8+
"Failed to fetch", // Chrome
9+
"NetworkError when attempting to fetch resource.", // Firefox
10+
"The Internet connection appears to be offline.", // Safari 16
11+
"Network request failed", // `cross-fetch`
12+
"fetch failed", // Undici (Node.js)
13+
"terminated", // Undici (Node.js)
14+
" A network error occurred.", // Bun (WebKit)
15+
"Network connection lost", // Cloudflare Workers (fetch)
16+
]);
17+
18+
export default function isNetworkError(error) {
19+
const isValid =
20+
error &&
21+
isError(error) &&
22+
error.name === "TypeError" &&
23+
typeof error.message === "string";
24+
25+
if (!isValid) {
26+
return false;
27+
}
28+
29+
const { message, stack } = error;
30+
31+
// Safari 17+ has generic message but no stack for network errors
32+
if (message === "Load failed") {
33+
return (
34+
stack === undefined ||
35+
// Sentry adds its own stack trace to the fetch error, so also check for that
36+
"__sentry_captured__" in error
37+
);
38+
}
39+
40+
// Deno network errors start with specific text
41+
if (message.startsWith("error sending request for url")) {
42+
return true;
43+
}
44+
45+
// Standard network error messages
46+
return errorMessages.has(message);
47+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
MIT License
2+
3+
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6+
7+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
export class AbortError extends Error {
2+
readonly name: "AbortError";
3+
readonly originalError: Error;
4+
5+
/**
6+
Abort retrying and reject the promise. No callback functions will be called.
7+
8+
@param message - An error message or a custom error.
9+
*/
10+
constructor(message: string | Error);
11+
}
12+
13+
export type RetryContext = {
14+
readonly error: Error;
15+
readonly attemptNumber: number;
16+
readonly retriesLeft: number;
17+
readonly retriesConsumed: number;
18+
};
19+
20+
export type Options = {
21+
/**
22+
Callback invoked on each failure. Receives a context object containing the error and retry state information.
23+
24+
The function is called before `shouldConsumeRetry` and `shouldRetry`, for all errors except `AbortError`.
25+
26+
The function is not called on `AbortError`.
27+
28+
@example
29+
```
30+
import pRetry from 'p-retry';
31+
32+
const run = async () => {
33+
const response = await fetch('https://sindresorhus.com/unicorn');
34+
35+
if (!response.ok) {
36+
throw new Error(response.statusText);
37+
}
38+
39+
return response.json();
40+
};
41+
42+
const result = await pRetry(run, {
43+
onFailedAttempt: ({error, attemptNumber, retriesLeft, retriesConsumed}) => {
44+
console.log(`Attempt ${attemptNumber} failed. ${retriesLeft} retries left. ${retriesConsumed} retries consumed.`);
45+
// 1st request => Attempt 1 failed. 5 retries left. 0 retries consumed.
46+
// 2nd request => Attempt 2 failed. 4 retries left. 1 retries consumed.
47+
// …
48+
},
49+
retries: 5
50+
});
51+
52+
console.log(result);
53+
```
54+
55+
The `onFailedAttempt` function can return a promise. For example, to add a [delay](https://github.com/sindresorhus/delay):
56+
57+
@example
58+
```
59+
import pRetry from 'p-retry';
60+
import delay from 'delay';
61+
62+
const run = async () => { … };
63+
64+
const result = await pRetry(run, {
65+
onFailedAttempt: async () => {
66+
console.log('Waiting for 1 second before retrying');
67+
await delay(1000);
68+
}
69+
});
70+
```
71+
72+
If the `onFailedAttempt` function throws, all retries will be aborted and the original promise will reject with the thrown error.
73+
*/
74+
readonly onFailedAttempt?: (context: RetryContext) => void | Promise<void>;
75+
76+
/**
77+
Decide if a retry should occur based on the context. Returning true triggers a retry, false aborts with the error.
78+
79+
The function is called after `onFailedAttempt` and `shouldConsumeRetry`.
80+
81+
The function is not called on `AbortError`, `TypeError` (except network errors), or if `retries` or `maxRetryTime` are exhausted.
82+
83+
@example
84+
```
85+
import pRetry from 'p-retry';
86+
87+
const run = async () => { … };
88+
89+
const result = await pRetry(run, {
90+
shouldRetry: ({error, attemptNumber, retriesLeft}) => !(error instanceof CustomError)
91+
});
92+
```
93+
94+
In the example above, the operation will be retried unless the error is an instance of `CustomError`.
95+
96+
If the `shouldRetry` function throws, all retries will be aborted and the original promise will reject with the thrown error.
97+
*/
98+
readonly shouldRetry?: (context: RetryContext) => boolean | Promise<boolean>;
99+
100+
/**
101+
Decide if this failure should consume a retry from the `retries` budget.
102+
103+
When `false` is returned, the failure will not consume a retry or increment backoff values, but is still subject to `maxRetryTime`.
104+
105+
The function is called after `onFailedAttempt`, but before `shouldRetry`.
106+
107+
The function is not called on `AbortError`.
108+
109+
@example
110+
```
111+
import pRetry from 'p-retry';
112+
113+
const run = async () => { … };
114+
115+
const result = await pRetry(run, {
116+
retries: 2,
117+
shouldConsumeRetry: ({error, retriesLeft}) => {
118+
console.log(`Retries left: ${retriesLeft}`);
119+
return !(error instanceof RateLimitError);
120+
},
121+
});
122+
```
123+
124+
In the example above, `RateLimitError`s will not decrement the available `retries`.
125+
126+
If the `shouldConsumeRetry` function throws, all retries will be aborted and the original promise will reject with the thrown error.
127+
*/
128+
readonly shouldConsumeRetry?: (
129+
context: RetryContext
130+
) => boolean | Promise<boolean>;
131+
132+
/**
133+
The maximum amount of times to retry the operation.
134+
135+
@default 10
136+
*/
137+
readonly retries?: number;
138+
139+
/**
140+
The exponential factor to use.
141+
142+
@default 2
143+
*/
144+
readonly factor?: number;
145+
146+
/**
147+
The number of milliseconds before starting the first retry.
148+
149+
Set this to `0` to retry immediately with no delay.
150+
151+
@default 1000
152+
*/
153+
readonly minTimeout?: number;
154+
155+
/**
156+
The maximum number of milliseconds between two retries.
157+
158+
@default Infinity
159+
*/
160+
readonly maxTimeout?: number;
161+
162+
/**
163+
Randomizes the timeouts by multiplying with a factor between 1 and 2.
164+
165+
@default false
166+
*/
167+
readonly randomize?: boolean;
168+
169+
/**
170+
The maximum time (in milliseconds) that the retried operation is allowed to run.
171+
172+
@default Infinity
173+
174+
Measured with a monotonic clock (`performance.now()`) so system clock adjustments do not affect the limit.
175+
*/
176+
readonly maxRetryTime?: number;
177+
178+
/**
179+
You can abort retrying using [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController).
180+
181+
```
182+
import pRetry from 'p-retry';
183+
184+
const run = async () => { … };
185+
const controller = new AbortController();
186+
187+
cancelButton.addEventListener('click', () => {
188+
controller.abort(new Error('User clicked cancel button'));
189+
});
190+
191+
try {
192+
await pRetry(run, {signal: controller.signal});
193+
} catch (error) {
194+
console.log(error.message);
195+
//=> 'User clicked cancel button'
196+
}
197+
```
198+
*/
199+
readonly signal?: AbortSignal;
200+
201+
/**
202+
Prevents retry timeouts from keeping the process alive.
203+
204+
Only affects platforms with a `.unref()` method on timeouts, such as Node.js.
205+
206+
@default false
207+
*/
208+
readonly unref?: boolean;
209+
};
210+
211+
/**
212+
Returns a `Promise` that is fulfilled when calling `input` returns a fulfilled promise. If calling `input` returns a rejected promise, `input` is called again until the max retries are reached, it then rejects with the last rejection reason.
213+
214+
Does not retry on most `TypeErrors`, with the exception of network errors. This is done on a best case basis as different browsers have different [messages](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful) to indicate this. See [whatwg/fetch#526 (comment)](https://github.com/whatwg/fetch/issues/526#issuecomment-554604080)
215+
216+
@param input - Receives the number of attempts as the first argument and is expected to return a `Promise` or any value.
217+
@param options - Options for configuring the retry behavior.
218+
219+
@example
220+
```
221+
import pRetry, {AbortError} from 'p-retry';
222+
223+
const run = async () => {
224+
const response = await fetch('https://sindresorhus.com/unicorn');
225+
226+
// Abort retrying if the resource doesn't exist
227+
if (response.status === 404) {
228+
throw new AbortError(response.statusText);
229+
}
230+
231+
return response.blob();
232+
};
233+
234+
console.log(await pRetry(run, {retries: 5}));
235+
```
236+
*/
237+
export default function pRetry<T>(
238+
input: (attemptNumber: number) => PromiseLike<T> | T,
239+
options?: Options
240+
): Promise<T>;
241+
242+
/**
243+
Wrap a function so that each call is automatically retried on failure.
244+
245+
@example
246+
```
247+
import {makeRetriable} from 'p-retry';
248+
249+
const fetchWithRetry = makeRetriable(fetch, {retries: 5});
250+
251+
const response = await fetchWithRetry('https://sindresorhus.com/unicorn');
252+
```
253+
*/
254+
export function makeRetriable<Arguments extends readonly unknown[], Result>(
255+
function_: (...arguments_: Arguments) => PromiseLike<Result> | Result,
256+
options?: Options
257+
): (...arguments_: Arguments) => Promise<Result>;

0 commit comments

Comments
 (0)