Do not inherit from body
Per spec, make Body a proper mixin
This commit is contained in:
parent
56f786f896
commit
f08b120771
232
src/body.js
232
src/body.js
|
@ -13,45 +13,46 @@ import Blob, {BUFFER} from './blob.js';
|
|||
import FetchError from './fetch-error.js';
|
||||
|
||||
const DISTURBED = Symbol('disturbed');
|
||||
const CONSUME_BODY = Symbol('consumeBody');
|
||||
|
||||
/**
|
||||
* Body class
|
||||
*
|
||||
* Cannot use ES6 class because Body must be called with .call().
|
||||
*
|
||||
* @param Stream body Readable stream
|
||||
* @param Object opts Response options
|
||||
* @return Void
|
||||
*/
|
||||
export default class Body {
|
||||
constructor(body, {
|
||||
size = 0,
|
||||
timeout = 0
|
||||
} = {}) {
|
||||
if (body == null) {
|
||||
// body is undefined or null
|
||||
body = null;
|
||||
} else if (typeof body === 'string') {
|
||||
// body is string
|
||||
} else if (body instanceof Blob) {
|
||||
// body is blob
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
// body is buffer
|
||||
} else if (bodyStream(body)) {
|
||||
// body is stream
|
||||
} else {
|
||||
// none of the above
|
||||
// coerce to string
|
||||
body = String(body);
|
||||
}
|
||||
this.body = body;
|
||||
this[DISTURBED] = false;
|
||||
this.size = size;
|
||||
this.timeout = timeout;
|
||||
export default function Body(body, {
|
||||
size = 0,
|
||||
timeout = 0
|
||||
} = {}) {
|
||||
if (body == null) {
|
||||
// body is undefined or null
|
||||
body = null;
|
||||
} else if (typeof body === 'string') {
|
||||
// body is string
|
||||
} else if (body instanceof Blob) {
|
||||
// body is blob
|
||||
} else if (Buffer.isBuffer(body)) {
|
||||
// body is buffer
|
||||
} else if (bodyStream(body)) {
|
||||
// body is stream
|
||||
} else {
|
||||
// none of the above
|
||||
// coerce to string
|
||||
body = String(body);
|
||||
}
|
||||
this.body = body;
|
||||
this[DISTURBED] = false;
|
||||
this.size = size;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
Body.prototype = {
|
||||
get bodyUsed() {
|
||||
return this[DISTURBED];
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Decode response as ArrayBuffer
|
||||
|
@ -59,8 +60,8 @@ export default class Body {
|
|||
* @return Promise
|
||||
*/
|
||||
arrayBuffer() {
|
||||
return this[CONSUME_BODY]().then(buf => toArrayBuffer(buf));
|
||||
}
|
||||
return consumeBody.call(this).then(buf => toArrayBuffer(buf));
|
||||
},
|
||||
|
||||
/**
|
||||
* Return raw response as Blob
|
||||
|
@ -69,7 +70,7 @@ export default class Body {
|
|||
*/
|
||||
blob() {
|
||||
let ct = this.headers && this.headers.get('content-type') || '';
|
||||
return this[CONSUME_BODY]().then(buf => Object.assign(
|
||||
return consumeBody.call(this).then(buf => Object.assign(
|
||||
// Prevent copying
|
||||
new Blob([], {
|
||||
type: ct.toLowerCase()
|
||||
|
@ -78,7 +79,7 @@ export default class Body {
|
|||
[BUFFER]: buf
|
||||
}
|
||||
));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Decode response as json
|
||||
|
@ -86,8 +87,8 @@ export default class Body {
|
|||
* @return Promise
|
||||
*/
|
||||
json() {
|
||||
return this[CONSUME_BODY]().then(buffer => JSON.parse(buffer.toString()));
|
||||
}
|
||||
return consumeBody.call(this).then(buffer => JSON.parse(buffer.toString()));
|
||||
},
|
||||
|
||||
/**
|
||||
* Decode response as text
|
||||
|
@ -95,8 +96,8 @@ export default class Body {
|
|||
* @return Promise
|
||||
*/
|
||||
text() {
|
||||
return this[CONSUME_BODY]().then(buffer => buffer.toString());
|
||||
}
|
||||
return consumeBody.call(this).then(buffer => buffer.toString());
|
||||
},
|
||||
|
||||
/**
|
||||
* Decode response as buffer (non-spec api)
|
||||
|
@ -104,8 +105,8 @@ export default class Body {
|
|||
* @return Promise
|
||||
*/
|
||||
buffer() {
|
||||
return this[CONSUME_BODY]();
|
||||
}
|
||||
return consumeBody.call(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Decode response as text, while automatically detecting the encoding and
|
||||
|
@ -114,94 +115,95 @@ export default class Body {
|
|||
* @return Promise
|
||||
*/
|
||||
textConverted() {
|
||||
return this[CONSUME_BODY]().then(buffer => convertBody(buffer, this.headers));
|
||||
return consumeBody.call(this).then(buffer => convertBody(buffer, this.headers));
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode buffers into utf-8 string
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
function consumeBody(body) {
|
||||
if (this[DISTURBED]) {
|
||||
return Body.Promise.reject(new Error(`body used already for: ${this.url}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode buffers into utf-8 string
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
[CONSUME_BODY]() {
|
||||
if (this[DISTURBED]) {
|
||||
return Body.Promise.reject(new Error(`body used already for: ${this.url}`));
|
||||
this[DISTURBED] = true;
|
||||
|
||||
// body is null
|
||||
if (this.body === null) {
|
||||
return Body.Promise.resolve(new Buffer(0));
|
||||
}
|
||||
|
||||
// body is string
|
||||
if (typeof this.body === 'string') {
|
||||
return Body.Promise.resolve(new Buffer(this.body));
|
||||
}
|
||||
|
||||
// body is blob
|
||||
if (this.body instanceof Blob) {
|
||||
return Body.Promise.resolve(this.body[BUFFER]);
|
||||
}
|
||||
|
||||
// body is buffer
|
||||
if (Buffer.isBuffer(this.body)) {
|
||||
return Body.Promise.resolve(this.body);
|
||||
}
|
||||
|
||||
// istanbul ignore if: should never happen
|
||||
if (!bodyStream(this.body)) {
|
||||
return Body.Promise.resolve(new Buffer(0));
|
||||
}
|
||||
|
||||
// body is stream
|
||||
// get ready to actually consume the body
|
||||
let accum = [];
|
||||
let accumBytes = 0;
|
||||
let abort = false;
|
||||
|
||||
return new Body.Promise((resolve, reject) => {
|
||||
let resTimeout;
|
||||
|
||||
// allow timeout on slow response body
|
||||
if (this.timeout) {
|
||||
resTimeout = setTimeout(() => {
|
||||
abort = true;
|
||||
reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout'));
|
||||
}, this.timeout);
|
||||
}
|
||||
|
||||
this[DISTURBED] = true;
|
||||
// handle stream error, such as incorrect content-encoding
|
||||
this.body.on('error', err => {
|
||||
reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err));
|
||||
});
|
||||
|
||||
// body is null
|
||||
if (this.body === null) {
|
||||
return Body.Promise.resolve(new Buffer(0));
|
||||
}
|
||||
|
||||
// body is string
|
||||
if (typeof this.body === 'string') {
|
||||
return Body.Promise.resolve(new Buffer(this.body));
|
||||
}
|
||||
|
||||
// body is blob
|
||||
if (this.body instanceof Blob) {
|
||||
return Body.Promise.resolve(this.body[BUFFER]);
|
||||
}
|
||||
|
||||
// body is buffer
|
||||
if (Buffer.isBuffer(this.body)) {
|
||||
return Body.Promise.resolve(this.body);
|
||||
}
|
||||
|
||||
// istanbul ignore if: should never happen
|
||||
if (!bodyStream(this.body)) {
|
||||
return Body.Promise.resolve(new Buffer(0));
|
||||
}
|
||||
|
||||
// body is stream
|
||||
// get ready to actually consume the body
|
||||
let accum = [];
|
||||
let accumBytes = 0;
|
||||
let abort = false;
|
||||
|
||||
return new Body.Promise((resolve, reject) => {
|
||||
let resTimeout;
|
||||
|
||||
// allow timeout on slow response body
|
||||
if (this.timeout) {
|
||||
resTimeout = setTimeout(() => {
|
||||
abort = true;
|
||||
reject(new FetchError(`Response timeout while trying to fetch ${this.url} (over ${this.timeout}ms)`, 'body-timeout'));
|
||||
}, this.timeout);
|
||||
this.body.on('data', chunk => {
|
||||
if (abort || chunk === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// handle stream error, such as incorrect content-encoding
|
||||
this.body.on('error', err => {
|
||||
reject(new FetchError(`Invalid response body while trying to fetch ${this.url}: ${err.message}`, 'system', err));
|
||||
});
|
||||
if (this.size && accumBytes + chunk.length > this.size) {
|
||||
abort = true;
|
||||
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.body.on('data', chunk => {
|
||||
if (abort || chunk === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.size && accumBytes + chunk.length > this.size) {
|
||||
abort = true;
|
||||
reject(new FetchError(`content size at ${this.url} over limit: ${this.size}`, 'max-size'));
|
||||
return;
|
||||
}
|
||||
|
||||
accumBytes += chunk.length;
|
||||
accum.push(chunk);
|
||||
});
|
||||
|
||||
this.body.on('end', () => {
|
||||
if (abort) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(resTimeout);
|
||||
resolve(Buffer.concat(accum));
|
||||
});
|
||||
accumBytes += chunk.length;
|
||||
accum.push(chunk);
|
||||
});
|
||||
}
|
||||
|
||||
this.body.on('end', () => {
|
||||
if (abort) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(resTimeout);
|
||||
resolve(Buffer.concat(accum));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ const PARSED_URL = Symbol('url');
|
|||
* @param Object init Custom options
|
||||
* @return Void
|
||||
*/
|
||||
export default class Request extends Body {
|
||||
export default class Request {
|
||||
constructor(input, init = {}) {
|
||||
let parsedURL;
|
||||
|
||||
|
@ -51,7 +51,7 @@ export default class Request extends Body {
|
|||
clone(input) :
|
||||
null;
|
||||
|
||||
super(inputBody, {
|
||||
Body.call(this, inputBody, {
|
||||
timeout: init.timeout || input.timeout || 0,
|
||||
size: init.size || input.size || 0
|
||||
});
|
||||
|
@ -101,6 +101,13 @@ export default class Request extends Body {
|
|||
}
|
||||
}
|
||||
|
||||
for (const name of Object.getOwnPropertyNames(Body.prototype)) {
|
||||
if (!(name in Request.prototype)) {
|
||||
const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
|
||||
Object.defineProperty(Request.prototype, name, desc);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(Request.prototype, Symbol.toStringTag, {
|
||||
value: 'RequestPrototype',
|
||||
writable: false,
|
||||
|
|
|
@ -16,9 +16,9 @@ import Body, { clone } from './body';
|
|||
* @param Object opts Response options
|
||||
* @return Void
|
||||
*/
|
||||
export default class Response extends Body {
|
||||
export default class Response {
|
||||
constructor(body = null, opts = {}) {
|
||||
super(body, opts);
|
||||
Body.call(this, body, opts);
|
||||
|
||||
this.url = opts.url;
|
||||
this.status = opts.status || 200;
|
||||
|
@ -58,6 +58,13 @@ export default class Response extends Body {
|
|||
}
|
||||
}
|
||||
|
||||
for (const name of Object.getOwnPropertyNames(Body.prototype)) {
|
||||
if (!(name in Response.prototype)) {
|
||||
const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
|
||||
Object.defineProperty(Response.prototype, name, desc);
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(Response.prototype, Symbol.toStringTag, {
|
||||
value: 'ResponsePrototype',
|
||||
writable: false,
|
||||
|
|
Loading…
Reference in New Issue