as-promise.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const getStream = require('get-stream');
  4. const is = require('@sindresorhus/is');
  5. const PCancelable = require('p-cancelable');
  6. const requestAsEventEmitter = require('./request-as-event-emitter');
  7. const {HTTPError, ParseError, ReadError} = require('./errors');
  8. const {options: mergeOptions} = require('./merge');
  9. const {reNormalize} = require('./normalize-arguments');
  10. const asPromise = options => {
  11. const proxy = new EventEmitter();
  12. const promise = new PCancelable((resolve, reject, onCancel) => {
  13. const emitter = requestAsEventEmitter(options);
  14. onCancel(emitter.abort);
  15. emitter.on('response', async response => {
  16. proxy.emit('response', response);
  17. const stream = is.null(options.encoding) ? getStream.buffer(response) : getStream(response, options);
  18. let data;
  19. try {
  20. data = await stream;
  21. } catch (error) {
  22. reject(new ReadError(error, options));
  23. return;
  24. }
  25. const limitStatusCode = options.followRedirect ? 299 : 399;
  26. response.body = data;
  27. try {
  28. for (const [index, hook] of Object.entries(options.hooks.afterResponse)) {
  29. // eslint-disable-next-line no-await-in-loop
  30. response = await hook(response, updatedOptions => {
  31. updatedOptions = reNormalize(mergeOptions(options, {
  32. ...updatedOptions,
  33. retry: 0,
  34. throwHttpErrors: false
  35. }));
  36. // Remove any further hooks for that request, because we we'll call them anyway.
  37. // The loop continues. We don't want duplicates (asPromise recursion).
  38. updatedOptions.hooks.afterResponse = options.hooks.afterResponse.slice(0, index);
  39. return asPromise(updatedOptions);
  40. });
  41. }
  42. } catch (error) {
  43. reject(error);
  44. return;
  45. }
  46. const {statusCode} = response;
  47. if (options.json && response.body) {
  48. try {
  49. response.body = JSON.parse(response.body);
  50. } catch (error) {
  51. if (statusCode >= 200 && statusCode < 300) {
  52. const parseError = new ParseError(error, statusCode, options, data);
  53. Object.defineProperty(parseError, 'response', {value: response});
  54. reject(parseError);
  55. return;
  56. }
  57. }
  58. }
  59. if (statusCode !== 304 && (statusCode < 200 || statusCode > limitStatusCode)) {
  60. const error = new HTTPError(response, options);
  61. Object.defineProperty(error, 'response', {value: response});
  62. if (emitter.retry(error) === false) {
  63. if (options.throwHttpErrors) {
  64. reject(error);
  65. return;
  66. }
  67. resolve(response);
  68. }
  69. return;
  70. }
  71. resolve(response);
  72. });
  73. emitter.once('error', reject);
  74. [
  75. 'request',
  76. 'redirect',
  77. 'uploadProgress',
  78. 'downloadProgress'
  79. ].forEach(event => emitter.on(event, (...args) => proxy.emit(event, ...args)));
  80. });
  81. promise.on = (name, fn) => {
  82. proxy.on(name, fn);
  83. return promise;
  84. };
  85. return promise;
  86. };
  87. module.exports = asPromise;