phin-retry

raw JSON →
2.0.0 verified Thu Apr 23 auth: no javascript

phin-retry is an ultra-lightweight Node.js HTTP client that wraps the `phin` library, adding robust retry and delay capabilities. It aims to provide a similar API experience to the now-deprecated `request-promise` library, making it familiar for developers transitioning from older HTTP clients. The current stable version is 2.0.0. While a specific release cadence isn't defined, it typically updates in response to `phin`'s evolution or community needs. Key differentiators include customizable retry strategies (based on network errors, HTTP status codes, or custom logic), configurable exponential or fixed delays between retries, and flexible error handling strategies. It supports standard HTTP methods (GET, POST, PUT, DELETE, PATCH) and exposes all underlying `phin` options, allowing for fine-grained control over requests. Its lightweight nature and focus on resilient API calls make it particularly suitable for microservices or applications requiring dependable external API interactions.

error TypeError: request.get is not a function
cause This usually indicates an incorrect import or require statement, or attempting to use `request` before it's properly assigned, or outside an async context when using `await`.
fix
Ensure you are using const request = require('phin-retry'); for CommonJS environments. If using ESM, try import request from 'phin-retry'; and confirm your file is treated as an ES module. Also, ensure await calls are within an async function.
error UnhandledPromiseRejectionWarning: Error: Bad status code: 404
cause The default `errorStrategy` in `phin-retry` throws an error for any response with a status code < 200 or >= 300 (e.g., 4xx, 5xx).
fix
Wrap the await request(...) call in a try...catch block to handle the thrown error, or provide a custom errorStrategy option in your request configuration to prevent phin-retry from throwing on specific status codes.
error ReferenceError: require is not defined
cause Attempting to use the CommonJS `require()` function in an ES Module (ESM) context.
fix
Change your import statement from const request = require('phin-retry'); to import request from 'phin-retry';. Additionally, ensure your package.json specifies "type": "module" or that the file ends with a .mjs extension.
breaking Responses with status codes less than 200 or greater than or equal to 300 are automatically thrown as errors by default, unlike some other HTTP clients (e.g., `request-promise`).
fix Implement a custom `errorStrategy` function in your request options to override the default behavior and handle non-2xx status codes as desired without throwing, or wrap calls in `try/catch` blocks.
gotcha The documentation primarily uses CommonJS `require()` syntax. While Node.js generally supports both, direct ESM `import` might require specific project configuration or different import paths depending on the package's module resolution.
fix For ESM projects, try `import request from 'phin-retry'` or `import * as request from 'phin-retry'`. If issues persist, consider using `require()` within an ESM wrapper or verify `package.json` `exports` field configuration.
gotcha By default, `phin-retry` only retries once on network errors or status codes >= 500, with a relatively short delay. This default behavior might not be sufficient for highly unstable external services or specific retry requirements.
fix Explicitly configure the `retry` option (number of retries), `delay` (milliseconds), and provide custom `retryStrategy` and `delayStrategy` functions in your request options to fine-tune retry behavior for specific endpoints or error types.
npm install phin-retry
yarn add phin-retry
pnpm add phin-retry

Demonstrates basic GET and POST requests, including custom retry logic for server errors and authentication with tailored error and delay strategies using a local dummy server and public API.

const request = require('phin-retry');
const http = require('http'); // For a local server example

// --- Dummy local server for demonstration purposes ---
const server = http.createServer((req, res) => {
  if (req.url === '/api/post' && req.method === 'POST') {
    let body = '';
    req.on('data', chunk => { body += chunk.toString(); });
    req.on('end', () => {
      console.log('Received POST body:', body);
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ received: JSON.parse(body), status: 'success' }));
    });
  } else if (req.url === '/api/delete' && req.method === 'DELETE') {
    const authHeader = req.headers.authorization;
    if (authHeader === 'Basic bmFtZTpzZWNyZXQ=') { // base64 for name:secret
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ status: 'deleted' }));
    } else {
      res.writeHead(401, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ status: 'unauthorized' }));
    }
  } else if (req.url === '/error' && req.method === 'GET') {
    const failCount = (server.failCount || 0);
    if (failCount < 2) { 
        server.failCount = failCount + 1;
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end('Internal Server Error (simulated)');
    } else {
        server.failCount = 0; 
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Success after retries!');
    }
  } else {
    res.writeHead(404);
    res.end();
  }
});

const PORT = 9393;
server.listen(PORT, async () => {
  console.log(`Dummy server running on http://localhost:${PORT}`);

  try {
    // Example 1: Simple GET request
    console.log('\n--- Example: Simple GET ---');
    const getResponse = await request.get('https://jsonplaceholder.typicode.com/posts/1');
    console.log('GET Response Status:', getResponse.statusCode);
    console.log('GET Response Body (first 100 chars):', getResponse.body.slice(0, 100));

    // Example 2: POST request with retry and delay
    console.log('\n--- Example: POST with Retry & Delay ---');
    const postResponse = await request.post({
      url: `http://localhost:${PORT}/api/post`,
      body: { msg: 'input' },
      retry: 3,
      delay: 500
    });
    console.log('POST Response Status:', postResponse.statusCode);
    console.log('POST Response Body:', JSON.parse(postResponse.body));

    // Example 3: DELETE request with custom retry/error/delay strategy
    console.log('\n--- Example: DELETE with Custom Strategies ---');
    const deleteResponse = await request.delete({
      url: `http://localhost:${PORT}/api/delete`,
      auth: { user: 'name', pass: 'secret' },
      errorStrategy: ({ response, error }) => {
        if (error) return true; // Retry on network error
        if (response.statusCode >= 400 && response.statusCode !== 401) return false; 
        return true; // Consider other 4xx codes as retryable
      },
      retryStrategy: ({ response, error, options }) => {
        if (error) return true;
        if (options.method === 'DELETE' && response.statusCode === 401) return true;
        if (response.statusCode >= 200 && response.statusCode < 300) return false;
        return true;
      },
      delayStrategy: ({ error }) => { if (error) return 5000; return 2000; },
      retry: 2 
    });
    console.log('DELETE Response Status:', deleteResponse.statusCode);
    console.log('DELETE Response Body:', JSON.parse(deleteResponse.body));

    // Example 4: GET with retries due to server error
    console.log('\n--- Example: GET with Server Error Retries ---');
    const errorRetryResponse = await request.get({
        url: `http://localhost:${PORT}/error`,
        retry: 3,
        delay: 200
    });
    console.log('Error Retry Response Status:', errorRetryResponse.statusCode);
    console.log('Error Retry Response Body:', errorRetryResponse.body);

  } catch (err) {
    console.error('\nAn error occurred during request examples:', err.message);
    if (err.response) {
      console.error('Error Response Status:', err.response.statusCode);
      console.error('Error Response Body:', err.response.body);
    }
  } finally {
    server.close(() => console.log('\nDummy server closed.'));
  }
});