Skip to content

Commit d0d4e53

Browse files
committed
📦 v 4.1.0
- Async API - Support for the latest node.js (20, 22 ,24) - Improved tests and test for async APIs - Dependencies: - `node-libcurl@4.10` (*was 4.0.0*) - Dev-dependencies - `chai@5.2.0` (*was: 5.1.1*) - `mocha@11.7.1` (*was: 10.6.8*)
1 parent 43242ae commit d0d4e53

5 files changed

Lines changed: 389 additions & 361 deletions

File tree

README.md

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const { status, headers, body } = await requestAsync({ url: 'https://api.example
8484

8585
## Note
8686

87-
We build this package to serve our needs and solve our issues with Node's native API. It may have a lack of compatibility with `request()` module API, or compatible only partially.
87+
We build this package to serve our needs and solve our issues with Node's native API. It may have a lack of compatibility with `request()` module API, or be compatible only partially. [PRs, suggestions, and discussions](https://github.com/veliovgroup/request-extra/issues) are always welcome.
8888

8989
## API
9090

@@ -229,7 +229,10 @@ __Notes__:
229229

230230
### Returns `req` *Object*
231231

232-
Using async API
232+
- `request()` and `requestAsync({ wait: true })` return `req` instance of {*LibCurlRequest*}
233+
- `requestAsync()` (*without wait*) and `req.sendAsync()` return {*Response*} upon resolve
234+
235+
#### Using async API
233236

234237
```js
235238
import { requestAsync } from 'request-libcurl';
@@ -243,13 +246,18 @@ try {
243246
}
244247

245248
// GET RESPONSE RIGHT AWAY
246-
const { statusCode, body, headers } = await requestAsync({ url: 'https://example.com' });
249+
try {
250+
const resp = await requestAsync({ url: 'https://example.com' });
251+
const { statusCode, body, headers } = resp;
252+
} catch (error) {
253+
const { errorCode, code, statusCode, message } = error;
254+
}
247255
```
248256

249257
- `req` {*LibCurlRequest*} - The *LibCurlRequest* instance returned only with `{ wait: true }` option, otherwise it returns *Response* (`resp` in the docs) right away
250-
- `req.abortAsync()` {*Promise*} - Abort current request, request will return `499: Client Closed Request` HTTP error
251-
- `req.sendAsync()` {*Promise*} - Send request, use it with `wait`. For example with `rawBody`/`noStorage`, when you need to delay sending request, for example to set event listeners and/or callbacks
252-
- `req.pipe(stream.Writable)` {*Function*} - Pipe response to a *WritableStream*, for example download a file to FS. Use with `{wait: true, retry: false}` options, and `.send()` method
258+
- `req.abortAsync()` {*Promise*} - Abort current request, request will throw `499: Client Closed Request` HTTP error
259+
- `req.sendAsync()` {*Promise*} - Send request, use it with `{ wait: true }`. For example with `rawBody`/`noStorage`, when you need to delay sending request, for example to set event listeners and/or callbacks
260+
- `req.pipe(stream.Writable)` {*Function*} - Pipe response to a *WritableStream*, for example download a file to FS. Use with `{wait: true, retry: false}` options, and `.sendAsync()` method
253261
- `req.onData(callback)` {*Function*} - Hook, called right after data is received, called for each data-chunk. Useful with `.pipe()`, `rawBody`/`noStorage` and callbacks/events
254262
- `req.onHeader(callback)` {*Function*} - Hook, called right after header is received, called for each header. Useful with `.pipe()`, `rawBody`/`noStorage` and callbacks/events
255263
- `resp` {*Response*} - Successful response
@@ -263,15 +271,15 @@ const { statusCode, body, headers } = await requestAsync({ url: 'https://example
263271
- `error.statusCode` {*Number*} - HTTP error code, if any;
264272
- `error.message` {*String*} - Human-readable error.
265273

266-
Using callback API
274+
#### Using callback API
267275

268276
```js
269277
import request from 'request-libcurl';
270278
const req = request({ url: 'https://example.com' }, callback);
271279
```
272280

273281
- `req.abort()` - Abort current request, request will return `499: Client Closed Request` HTTP error
274-
- `req.send()` - Send request, use it with `wait`. For example with `rawBody`/`noStorage`, when you need to delay sending request, for example to set event listeners and/or callbacks
282+
- `req.send()` - Send request, use it with `{ wait: true }`. For example with `rawBody`/`noStorage`, when you need to delay sending request, for example to set event listeners and/or callbacks
275283
- `callback(error, resp)` - Callback triggered on successful response
276284
- `error` {*undefined*};
277285
- `resp` {*Response*}

index.cjs

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use strict';
22

3-
const fs = require('fs');
4-
const url = require('url');
3+
Object.defineProperty(exports, '__esModule', { value: true });
4+
5+
const fs = require('node:fs');
6+
const node_url = require('node:url');
57
const nodeLibcurl = require('node-libcurl');
68

79
const SSL_ERROR_CODES = [58, 60, 83, 90, 91];
@@ -42,7 +44,7 @@ const closeCurl = (curl) => {
4244
if (curl && curl.close) {
4345
curl.close.call(curl);
4446
}
45-
} catch (err) {
47+
} catch (_err) {
4648
// we are good here
4749
}
4850
};
@@ -52,8 +54,8 @@ const sendRequest = (libcurl, url, cb) => {
5254

5355
closeCurl(libcurl.curl);
5456

55-
const opts = libcurl.opts;
5657
const curl = new nodeLibcurl.Curl();
58+
const opts = libcurl.opts;
5759
let finished = false;
5860
let timeoutTimer = null;
5961
let isJsonUpload = false;
@@ -197,6 +199,7 @@ const sendRequest = (libcurl, url, cb) => {
197199
curl.on('end', (statusCode, body, _headers) => {
198200
libcurl._debug('[END EVENT]', opts.retries, url.href, finished, statusCode);
199201
stopRequestTimeout();
202+
curl.removeAllListeners();
200203
if (finished) { return; }
201204
finished = true;
202205

@@ -253,6 +256,7 @@ const sendRequest = (libcurl, url, cb) => {
253256
curl.on('error', (error, errorCode) => {
254257
libcurl._debug('REQUEST ERROR:', opts.retries, url.href, {error, errorCode});
255258
stopRequestTimeout();
259+
curl.removeAllListeners();
256260
if (finished) { return; }
257261

258262
finished = true;
@@ -389,6 +393,13 @@ class LibCurlRequest {
389393
throw new TypeError('{opts} expecting an Object as first argument');
390394
}
391395

396+
if (!cb && opts.isPromise) {
397+
this.promise = new Promise((resolve, reject) => {
398+
this._resolve = resolve;
399+
this._reject = reject;
400+
});
401+
}
402+
392403
this.cb = typeof cb === 'function' ? cb : noop;
393404
this.sent = false;
394405
this.pipeTo = [];
@@ -423,7 +434,7 @@ class LibCurlRequest {
423434
isBadUrl = true;
424435
} else {
425436
try {
426-
this.url = new url.URL(this.opts.url);
437+
this.url = new node_url.URL(this.opts.url);
427438
} catch (urlError) {
428439
this._debug('REQUEST: `new URL()` ERROR:', opts, urlError);
429440
isBadUrl = true;
@@ -434,7 +445,11 @@ class LibCurlRequest {
434445
this.sent = true;
435446
this.finished = true;
436447
process.nextTick(() => {
437-
this.cb(badUrlError);
448+
if (this.opts.isPromise) {
449+
this._reject(badUrlError);
450+
} else {
451+
this.cb(badUrlError);
452+
}
438453
});
439454
return;
440455
}
@@ -514,9 +529,17 @@ class LibCurlRequest {
514529
this.finished = true;
515530
this._stopRequestTimeout();
516531
if (error) {
517-
this.cb(error);
532+
if (this.opts.isPromise) {
533+
this._reject(error);
534+
} else {
535+
this.cb(error);
536+
}
518537
} else {
519-
this.cb(void 0, result);
538+
if (this.opts.isPromise) {
539+
this._resolve(result);
540+
} else {
541+
this.cb(void 0, result);
542+
}
520543
}
521544
}
522545
}
@@ -535,9 +558,18 @@ class LibCurlRequest {
535558
return this;
536559
}
537560

561+
async sendAsync() {
562+
if (!this.opts.isPromise) {
563+
throw new Error('Calling .sendAsync() on non-async API, use requestAsync() to invoke async API');
564+
}
565+
this.send();
566+
return this.promise;
567+
}
568+
538569
abort() {
539570
this._debug('[abort]', this.opts.url);
540571
this._stopRequestTimeout();
572+
this.curl?.removeAllListeners?.();
541573

542574
if (this.retryTimer) {
543575
clearTimeout(this.retryTimer);
@@ -548,16 +580,37 @@ class LibCurlRequest {
548580
closeCurl(this.curl);
549581

550582
this.finished = true;
551-
this.cb(abortError);
583+
if (this.opts.isPromise) {
584+
this._reject(abortError);
585+
} else {
586+
this.cb(abortError);
587+
}
552588
}
553589
return this;
554590
}
591+
592+
async abortAsync() {
593+
if (!this.opts.isPromise) {
594+
throw new Error('Calling .abortAsync() on non-async API, use requestAsync() to invoke async API');
595+
}
596+
this.abort();
597+
return this.promise;
598+
}
555599
}
556600

557601
function request (opts, cb) {
558602
return new LibCurlRequest(opts, cb);
559603
}
560604

605+
async function requestAsync (opts) {
606+
if (opts.wait) {
607+
return new LibCurlRequest(Object.assign({}, opts, { isPromise: true }));
608+
}
609+
610+
const req = new LibCurlRequest(Object.assign({}, opts, { isPromise: true }));
611+
return req.promise;
612+
}
613+
561614
request.defaultOptions = {
562615
wait: false,
563616
proxy: false,
@@ -579,9 +632,10 @@ request.defaultOptions = {
579632
return badStatuses.includes(statusCode) || statusCode >= 500;
580633
},
581634
headers: {
582-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
635+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
583636
Accept: '*/*'
584637
}
585638
};
586639

587-
module.exports = request;
640+
exports.default = request;
641+
exports.requestAsync = requestAsync;

index.js

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import fs from 'fs';
2-
import { URL } from 'url';
1+
import fs from 'node:fs';
2+
import { URL } from 'node:url';
33
import { Curl, CurlFeature } from 'node-libcurl';
44
const SSL_ERROR_CODES = [58, 60, 83, 90, 91];
55
const CURL_ERROR_CODES = [3, 4, 47];
@@ -39,7 +39,7 @@ const closeCurl = (curl) => {
3939
if (curl && curl.close) {
4040
curl.close.call(curl);
4141
}
42-
} catch (err) {
42+
} catch (_err) {
4343
// we are good here
4444
}
4545
};
@@ -49,8 +49,8 @@ const sendRequest = (libcurl, url, cb) => {
4949

5050
closeCurl(libcurl.curl);
5151

52-
const opts = libcurl.opts;
5352
const curl = new Curl();
53+
const opts = libcurl.opts;
5454
let finished = false;
5555
let timeoutTimer = null;
5656
let isJsonUpload = false;
@@ -194,6 +194,7 @@ const sendRequest = (libcurl, url, cb) => {
194194
curl.on('end', (statusCode, body, _headers) => {
195195
libcurl._debug('[END EVENT]', opts.retries, url.href, finished, statusCode);
196196
stopRequestTimeout();
197+
curl.removeAllListeners();
197198
if (finished) { return; }
198199
finished = true;
199200

@@ -250,6 +251,7 @@ const sendRequest = (libcurl, url, cb) => {
250251
curl.on('error', (error, errorCode) => {
251252
libcurl._debug('REQUEST ERROR:', opts.retries, url.href, {error, errorCode});
252253
stopRequestTimeout();
254+
curl.removeAllListeners();
253255
if (finished) { return; }
254256

255257
finished = true;
@@ -386,6 +388,13 @@ class LibCurlRequest {
386388
throw new TypeError('{opts} expecting an Object as first argument');
387389
}
388390

391+
if (!cb && opts.isPromise) {
392+
this.promise = new Promise((resolve, reject) => {
393+
this._resolve = resolve;
394+
this._reject = reject;
395+
});
396+
}
397+
389398
this.cb = typeof cb === 'function' ? cb : noop;
390399
this.sent = false;
391400
this.pipeTo = [];
@@ -431,7 +440,11 @@ class LibCurlRequest {
431440
this.sent = true;
432441
this.finished = true;
433442
process.nextTick(() => {
434-
this.cb(badUrlError);
443+
if (this.opts.isPromise) {
444+
this._reject(badUrlError);
445+
} else {
446+
this.cb(badUrlError);
447+
}
435448
});
436449
return;
437450
}
@@ -511,9 +524,17 @@ class LibCurlRequest {
511524
this.finished = true;
512525
this._stopRequestTimeout();
513526
if (error) {
514-
this.cb(error);
527+
if (this.opts.isPromise) {
528+
this._reject(error);
529+
} else {
530+
this.cb(error);
531+
}
515532
} else {
516-
this.cb(void 0, result);
533+
if (this.opts.isPromise) {
534+
this._resolve(result);
535+
} else {
536+
this.cb(void 0, result);
537+
}
517538
}
518539
}
519540
}
@@ -532,9 +553,18 @@ class LibCurlRequest {
532553
return this;
533554
}
534555

556+
async sendAsync() {
557+
if (!this.opts.isPromise) {
558+
throw new Error('Calling .sendAsync() on non-async API, use requestAsync() to invoke async API');
559+
}
560+
this.send();
561+
return this.promise;
562+
}
563+
535564
abort() {
536565
this._debug('[abort]', this.opts.url);
537566
this._stopRequestTimeout();
567+
this.curl?.removeAllListeners?.();
538568

539569
if (this.retryTimer) {
540570
clearTimeout(this.retryTimer);
@@ -545,16 +575,37 @@ class LibCurlRequest {
545575
closeCurl(this.curl);
546576

547577
this.finished = true;
548-
this.cb(abortError);
578+
if (this.opts.isPromise) {
579+
this._reject(abortError);
580+
} else {
581+
this.cb(abortError);
582+
}
549583
}
550584
return this;
551585
}
586+
587+
async abortAsync() {
588+
if (!this.opts.isPromise) {
589+
throw new Error('Calling .abortAsync() on non-async API, use requestAsync() to invoke async API');
590+
}
591+
this.abort();
592+
return this.promise;
593+
}
552594
}
553595

554596
function request (opts, cb) {
555597
return new LibCurlRequest(opts, cb);
556598
}
557599

600+
async function requestAsync (opts) {
601+
if (opts.wait) {
602+
return new LibCurlRequest(Object.assign({}, opts, { isPromise: true }));
603+
}
604+
605+
const req = new LibCurlRequest(Object.assign({}, opts, { isPromise: true }));
606+
return req.promise;
607+
}
608+
558609
request.defaultOptions = {
559610
wait: false,
560611
proxy: false,
@@ -576,9 +627,10 @@ request.defaultOptions = {
576627
return badStatuses.includes(statusCode) || statusCode >= 500;
577628
},
578629
headers: {
579-
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36',
630+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
580631
Accept: '*/*'
581632
}
582633
};
583634

584635
export default request;
636+
export { requestAsync };

0 commit comments

Comments
 (0)