UNPKG

20.5 kBMarkdownView Raw
1<a href="http://promisesaplus.com/">
2 <img src="https://promises-aplus.github.io/promises-spec/assets/logo-small.png"
3 align="right" valign="top" alt="Promises/A+ logo" />
4</a>
5
6# Request-Promise
7
8[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/request/request-promise?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [![Build Status](https://travis-ci.org/request/request-promise.svg?branch=master)](https://travis-ci.org/request/request-promise) [![Coverage Status](https://coveralls.io/repos/request/request-promise/badge.svg?branch=master&service=github)](https://coveralls.io/github/request/request-promise?branch=master) [![Dependency Status](https://david-dm.org/request/request-promise.svg)](https://david-dm.org/request/request-promise)
9
10The world-famous HTTP client "Request" now Promises/A+ compliant. Powered by Bluebird.
11
12[Bluebird](https://github.com/petkaantonov/bluebird) and [Request](https://github.com/mikeal/request) are pretty awesome, but I found myself using the same design pattern. Request-Promise adds a Bluebird-powered `.then(...)` method to Request call objects. By default, http response codes other than 2xx will cause the promise to be rejected. This can be overwritten by setting `options.simple` to `false`.
13
14## Installation
15
16This module is installed via npm:
17
18``` bash
19npm install request-promise
20```
21
22Request-Promise depends on loosely defined versions of Request and Bluebird. If you want to use specific versions of those modules please install them beforehand.
23
24## Cheat Sheet
25
26``` js
27var rp = require('request-promise');
28```
29
30### Crawl a webpage
31
32``` js
33rp('http://www.google.com')
34 .then(function (htmlString) {
35 // Process html...
36 })
37 .catch(function (err) {
38 // Crawling failed...
39 });
40```
41
42### Crawl a webpage better
43
44``` js
45var cheerio = require('cheerio'); // Basically jQuery for node.js
46
47var options = {
48 uri: 'http://www.google.com',
49 transform: function (body) {
50 return cheerio.load(body);
51 }
52};
53
54rp(options)
55 .then(function ($) {
56 // Process html like you would with jQuery...
57 })
58 .catch(function (err) {
59 // Crawling failed or Cheerio choked...
60 });
61```
62
63### GET something from a JSON REST API
64
65``` js
66var options = {
67 uri: 'https://api.github.com/user/repos',
68 qs: {
69 access_token: 'xxxxx xxxxx' // -> uri + '?access_token=xxxxx%20xxxxx'
70 },
71 headers: {
72 'User-Agent': 'Request-Promise'
73 },
74 json: true // Automatically parses the JSON string in the response
75};
76
77rp(options)
78 .then(function (repos) {
79 console.log('User has %d repos', repos.length);
80 })
81 .catch(function (err) {
82 // API call failed...
83 });
84```
85
86### POST data to a JSON REST API
87
88``` js
89var options = {
90 method: 'POST',
91 uri: 'http://posttestserver.com/post.php',
92 body: {
93 some: 'payload'
94 },
95 json: true // Automatically stringifies the body to JSON
96};
97
98rp(options)
99 .then(function (parsedBody) {
100 // POST succeeded...
101 })
102 .catch(function (err) {
103 // POST failed...
104 });
105```
106
107### POST like HTML forms do
108
109``` js
110var options = {
111 method: 'POST',
112 uri: 'http://posttestserver.com/post.php',
113 form: {
114 some: 'payload' // Will be urlencoded
115 },
116 headers: {
117 /* 'content-type': 'application/x-www-form-urlencoded' */ // Set automatically
118 }
119};
120
121rp(options)
122 .then(function (body) {
123 // POST succeeded...
124 })
125 .catch(function (err) {
126 // POST failed...
127 });
128```
129
130### Get the full response instead of just the body
131
132``` js
133var options = {
134 method: 'DELETE',
135 uri: 'http://my-server/path/to/resource/1234',
136 resolveWithFullResponse: true // <--- <--- <--- <---
137};
138
139rp(options)
140 .then(function (response) {
141 console.log("DELETE succeeded with status %d", response.statusCode);
142 })
143 .catch(function (err) {
144 // Delete failed...
145 });
146```
147
148### Get a rejection only if the request failed for technical reasons
149
150``` js
151var options = {
152 uri: 'http://www.google.com/this-page-does-not-exist.html',
153 simple: false // <--- <--- <--- <---
154};
155
156rp(options)
157 .then(function (body) {
158 // Request succeeded but might as well be a 404
159 // Usually combined with resolveWithFullResponse = true to check response.statusCode
160 })
161 .catch(function (err) {
162 // Request failed due to technical reasons...
163 });
164```
165
166---
167
168**For more options checkout the [Request docs](https://github.com/request/request#requestoptions-callback).**
169
170---
171
172## API in Detail
173
174Consider Request-Promise being:
175
176- A Request object
177 - With an [identical API](https://github.com/request/request): `require('request-promise') == require('request')` so to say
178 - However, the **USE OF STREAMS** (e.g. `.pipe(...)`) is **DISCOURAGED** because Request-Promise would grow the memory footprint for large requests unnecessarily high. Use the original Request library for that. You can use both libraries in the same project.
179- Plus some methods on a request call object:
180 - `rp(...).then(...)` or e.g. `rp.post(...).then(...)` which turn `rp(...)` and `rp.post(...)` into promises
181 - `rp(...).catch(...)` or e.g. `rp.del(...).catch(...)` which is the same method as provided by Bluebird promises
182 - `rp(...).finally(...)` or e.g. `rp.put(...).finally(...)` which is the same method as provided by Bluebird promises
183 - `rp(...).promise()` or e.g. `rp.head(...).promise()` which returns the underlying promise so you can access the full [Bluebird API](https://github.com/petkaantonov/bluebird/blob/master/API.md)
184- Plus some additional options:
185 - `simple` which is a boolean to set whether status codes other than 2xx should also reject the promise
186 - `resolveWithFullResponse` which is a boolean to set whether the promise should be resolve with the full response or just the response body
187 - `transform` which takes a function to transform the response into a custom value with which the promise is resolved
188
189The objects returned by request calls like `rp(...)` or e.g. `rp.post(...)` are regular Promises/A+ compliant promises and can be assimilated by any compatible promise library.
190
191The methods `.then(...)`, `.catch(...)`, and `.finally(...)` - which you can call on the request call objects - return a full-fledged Bluebird promise. That means you have the full [Bluebird API](https://github.com/petkaantonov/bluebird/blob/master/API.md) available for further chaining. E.g.: `rp(...).then(...).spread(...)` If, however, you need a method other than `.then(...)`, `.catch(...)`, or `.finally(...)` to be **FIRST** in the chain, use `.promise()`: `rp(...).promise().bind(...).then(...)`
192
193### .then(onFulfilled, onRejected)
194
195``` js
196// As a Request user you would write:
197var request = require('request');
198
199request('http://google.com', function (err, response, body) {
200 if (err) {
201 handleError({ error: err, response: response, ... });
202 } else if (!(/^2/.test('' + response.statusCode))) { // Status Codes other than 2xx
203 handleError({ error: body, response: response, ... });
204 } else {
205 process(body);
206 }
207});
208
209// As a Request-Promise user you can now write the equivalent code:
210var rp = require('request-promise');
211
212rp('http://google.com')
213 .then(process, handleError);
214```
215
216``` js
217// The same is available for all http method shortcuts:
218request.post('http://example.com/api', function (err, response, body) { ... });
219rp.post('http://example.com/api').then(...);
220```
221
222### .catch(onRejected)
223
224``` js
225rp('http://google.com')
226 .catch(handleError);
227
228// ... is syntactical sugar for:
229
230rp('http://google.com')
231 .then(null, handleError);
232
233
234// By the way, this:
235rp('http://google.com')
236 .then(process)
237 .catch(handleError);
238
239// ... is equivalent to:
240rp('http://google.com')
241 .then(process, handleError);
242```
243
244### .finally(onFinished)
245
246``` js
247rp('http://google.com')
248 .finally(function () {
249 // This is called after the request finishes either successful or not successful.
250 });
251```
252
253### .promise() - For advanced use cases
254
255In order to not pollute the Request call objects with the methods of the underlying Bluebird promise, only `.then(...)`, `.catch(...)`, and `.finally(...)` were exposed to cover most use cases. The effect is that any methods of a Bluebird promise other than `.then(...)`, `.catch(...)`, or `.finally(...)` cannot be used as the **FIRST** method in the promise chain:
256
257``` js
258// This works:
259rp('http://google.com').then(function () { ... });
260rp('http://google.com').catch(function () { ... });
261
262// This works as well since additional methods are only used AFTER the FIRST call in the chain:
263rp('http://google.com').then(function () { ... }).spread(function () { ... });
264rp('http://google.com').catch(function () { ... }).error(function () { ... });
265
266// Using additional methods as the FIRST call in the chain does not work:
267// rp('http://google.com').bind(this).then(function () { ... });
268
269// Use .promise() in these cases:
270rp('http://google.com').promise().bind(this).then(function () { ... });
271```
272
273### Fulfilled promises and the `resolveWithFullResponse` option
274
275``` js
276// Per default the body is passed to the fulfillment handler:
277rp('http://google.com')
278 .then(function (body) {
279 // Process the html of the Google web page...
280 });
281
282// The resolveWithFullResponse options allows to pass the full response:
283rp({ uri: 'http://google.com', resolveWithFullResponse: true })
284 .then(function (response) {
285 // Access response.statusCode, response.body etc.
286 });
287
288```
289
290### Rejected promises and the `simple` option
291
292``` js
293// The rejection handler is called with a reason object...
294rp('http://google.com')
295 .catch(function (reason) {
296 // Handle failed request...
297 });
298
299// ... and would be equivalent to this Request-only implementation:
300var options = { uri: 'http://google.com' };
301
302request(options, function (err, response, body) {
303 var reason;
304 if (err) {
305 reason = {
306 cause: err,
307 error: err,
308 options: options,
309 response: response
310 };
311 } else if (!(/^2/.test('' + response.statusCode))) { // Status Codes other than 2xx
312 reason = {
313 statusCode: response.statusCode,
314 error: body,
315 options: options,
316 response: response
317 };
318 }
319
320 if (reason) {
321 // Handle failed request...
322 }
323});
324
325
326// If you pass the simple option as false...
327rp({ uri: 'http://google.com', simple: false })
328 .catch(function (reason) {
329 // Handle failed request...
330 });
331
332// ... the equivalent Request-only code would be:
333request(options, function (err, response, body) {
334 if (err) {
335 var reason = {
336 cause: err,
337 error: err,
338 options: options,
339 response: response
340 };
341 // Handle failed request...
342 }
343});
344// E.g. a 404 would now fulfill the promise.
345// Combine it with resolveWithFullResponse = true to check the status code in the fulfillment handler.
346```
347
348With version 0.4 the reason objects became Error objects with identical properties to ensure backwards compatibility. These new Error types allow targeted catch blocks:
349
350``` js
351var errors = require('request-promise/errors');
352
353rp('http://google.com')
354 .catch(errors.StatusCodeError, function (reason) {
355 // The server responded with a status codes other than 2xx.
356 // Check reason.response.statusCode.
357 })
358 .catch(errors.RequestError, function (reason) {
359 // The request failed due to technical reasons.
360 // reason.cause is the Error object Request would pass into a callback.
361 });
362```
363
364### The `transform` function
365
366You can pass a function to `options.transform` to generate a custom fulfillment value when the promise gets resolved.
367
368``` js
369// Just for fun you could reverse the response body:
370var options = {
371 uri: 'http://google.com',
372 transform: function (body, response, resolveWithFullResponse) {
373 return body.split('').reverse().join('');
374 }
375};
376
377rp(options)
378 .then(function (reversedBody) {
379 // ;D
380 });
381
382
383// However, you could also do something useful:
384var $ = require('cheerio'); // Basically jQuery for node.js
385
386function autoParse(body, response, resolveWithFullResponse) {
387 // FIXME: The content type string could contain additional values like the charset.
388 if (response.headers['content-type'] === 'application/json') {
389 return JSON.parse(body);
390 } else if (response.headers['content-type'] === 'text/html') {
391 return $.load(body);
392 } else {
393 return body;
394 }
395}
396
397options.transform = autoParse;
398
399rp(options)
400 .then(function (autoParsedBody) {
401 // :)
402 });
403
404
405// You can go one step further and set the transform as the default:
406var rpap = rp.defaults({ transform: autoParse });
407
408rpap('http://google.com')
409 .then(function (autoParsedBody) {
410 // :)
411 });
412
413rpap('http://echojs.com')
414 .then(function (autoParsedBody) {
415 // =)
416 });
417```
418
419The third `resolveWithFullResponse` parameter of the transform function is equivalent to the option passed with the request. This allows to distinguish whether just the transformed body or the whole response shall be returned by the transform function:
420
421``` js
422function reverseBody(body, response, resolveWithFullResponse) {
423 response.body = response.body.split('').reverse().join('');
424 return resolveWithFullResponse ? response : response.body;
425}
426```
427
428## Experimental Support for Continuation Local Storage
429
430Continuation Local Storage (CLS) is a great mechanism for backpacking data along asynchronous call chains that is best explained in [these slides](http://fredkschott.com/post/2014/02/conquering-asynchronous-context-with-cls/). If you want to use CLS you need to install the [continuation-local-storage package](https://www.npmjs.com/package/continuation-local-storage). Request-Promise internally uses the [cls-bluebird package](https://www.npmjs.com/package/cls-bluebird) to enable CLS also within the Bluebird promises.
431
432Just call `rp.bindCLS(ns)` **ONCE** before your first request to activate CLS:
433``` js
434var rp = require('request-promise');
435var cls = require('continuation-local-storage');
436
437var ns = cls.createNamespace('testNS');
438rp.bindCLS(ns);
439
440ns.run(function () {
441 ns.set('value', 'hi');
442
443 rp('http://google.com')
444 .then(function () {
445 console.log(ns.get('value')); // -> hi
446 });
447});
448```
449
450Since the [cls-bluebird package](https://www.npmjs.com/package/cls-bluebird) currently is just a quick and dirty implementation the CLS support is only experimental.
451
452## Debugging
453
454The ways to debug the operation of Request-Promise are the same [as described](https://github.com/request/request#debugging) for Request. These are:
455
4561. Launch the node process like `NODE_DEBUG=request node script.js` (`lib,request,otherlib` works too).
4572. Set `require('request-promise').debug = true` at any time (this does the same thing as #1).
4583. Use the [request-debug module](https://github.com/nylen/request-debug) to view request and response headers and bodies. Instrument Request-Promise with `require('request-debug')(rp);`.
459
460## Mocking Request-Promise
461
462Usually you want to mock the whole request function which is returned by `require('request-promise')`. This is not possible by using a mocking library like [sinon.js](http://sinonjs.org) alone. What you need is a library that ties into the module loader and makes sure that your mock is returned whenever the tested code is calling `require('request-promise')`. [Mockery](https://github.com/mfncooper/mockery) is one of such libraries.
463
464@florianschmidt1994 kindly shared his solution:
465```javascript
466before(function (done) {
467
468 var filename = "fileForResponse";
469 mockery.enable({
470 warnOnReplace: false,
471 warnOnUnregistered: false,
472 useCleanCache: true
473 });
474
475 mockery.registerMock('request-promise', function () {
476 var response = fs.readFileSync(__dirname + '/data/' + filename, 'utf8');
477 return Bluebird.resolve(response.trim());
478 });
479
480 done();
481});
482
483after(function (done) {
484 mockery.disable();
485 mockery.deregisterAll();
486 done();
487});
488
489describe('custom test case', function () {
490 // Test some function/module/... which uses request-promise
491 // and it will always receive the predefined "fileForResponse" as data, e.g.:
492 var rp = require('request-promise');
493 rp(...).then(function(data) {
494 // ➞ data is what is in fileForResponse
495 });
496});
497```
498
499Based on that you may now build a more sophisticated mock. [Sinon.js](http://sinonjs.org) may be of help as well.
500
501## Contributing
502
503To set up your development environment:
504
5051. clone the repo to your desktop,
5062. in the shell `cd` to the main folder,
5073. hit `npm install`,
5084. hit `npm install gulp -g` if you haven't installed gulp globally yet, and
5095. run `gulp dev`. (Or run `node ./node_modules/.bin/gulp dev` if you don't want to install gulp globally.)
510
511`gulp dev` watches all source files and if you save some changes it will lint the code and execute all tests. The test coverage report can be viewed from `./coverage/lcov-report/index.html`.
512
513If you want to debug a test you should use `gulp test-without-coverage` to run all tests without obscuring the code by the test coverage instrumentation.
514
515## Change History
516
517- v1.0.0 (2015-10-11)
518 - **Braking Change**: Some errors that were previously thrown synchronously - e.g. for wrong input parameters - are now passed to the rejected promise instead
519 *(Thanks to @josnidhin for suggesting that in [issue #43](https://github.com/request/request-promise/issues/43))*
520 - For HEAD requests the headers instead of an empty body is returned (unless `resolveWithFullResponse = true` is used)
521 *(Thanks to @zcei for proposing the change in [issue #58](https://github.com/request/request-promise/issues/58))*
522 - Extended `transform` function by a third `resolveWithFullResponse` parameter
523 - Added experimental support for continuation local storage
524 *(Thanks to @silverbp preparing this in [issue #64](https://github.com/request/request-promise/issues/64))*
525 - Added node.js 4 to the Travis CI build
526 - Updated the README
527 *(Thanks to many people for their feedback in issues [#55](https://github.com/request/request-promise/issues/55) and [#59](https://github.com/request/request-promise/issues/59))*
528- v0.4.3 (2015-07-27)
529 - Reduced overhead by just requiring used lodash functions instead of the whole lodash library
530 *(Thanks to @luanmuniz for [pull request #54](https://github.com/request/request-promise/pull/54))*
531 - Updated dependencies
532- v0.4.2 (2015-04-12)
533 - Updated dependencies
534- v0.4.1 (2015-03-20)
535 - Improved Error types to work in browsers without v8 engine
536 *(Thanks to @nodiis for [pull request #40](https://github.com/request/request-promise/pull/40))*
537- v0.4.0 (2015-02-08)
538 - Introduced Error types used for the reject reasons (See last part [this section](#rejected-promises-and-the-simple-option))
539 *(Thanks to @jakecraige for starting the discussion in [issue #38](https://github.com/request/request-promise/issues/38))*
540 - **Minor Braking Change:** The reject reason objects became actual Error objects. However, `typeof reason === 'object'` still holds true and the error objects have the same properties as the previous reason objects. If the reject handler only accesses the properties on the reason object - which is usually the case - no migration is required.
541 - Added io.js and node.js 0.12 to the Travis CI build
542- v0.3.3 (2015-01-19)
543 - Fixed handling possibly unhandled rejections to work with the latest version of Bluebird
544 *(Thanks to @slang800 for reporting this in [issue #36](https://github.com/request/request-promise/issues/36))*
545- v0.3.2 (2014-11-17)
546 - Exposed `.finally(...)` to allow using it as the first method in the promise chain
547 *(Thanks to @hjpbarcelos for his [pull request #28](https://github.com/request/request-promise/pull/28))*
548- v0.3.1 (2014-11-11)
549 - Added the `.promise()` method for advanced Bluebird API usage
550 *(Thanks to @devo-tox for his feedback in [issue #27](https://github.com/request/request-promise/issues/27))*
551- v0.3.0 (2014-11-10)
552 - Carefully rewritten from scratch to make Request-Promise a drop-in replacement for Request
553
554## MIT Licensed
555
556See the [LICENSE file](LICENSE) for details.