Comparing version 0.2.2 to 0.3.0
{ | ||
"name": "popsicle", | ||
"main": "popsicle.js", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"homepage": "https://github.com/blakeembrey/popsicle", | ||
@@ -6,0 +6,0 @@ "authors": [ |
@@ -5,3 +5,3 @@ var gulp = require('gulp'); | ||
/** | ||
* Require all local grunt tasks. | ||
* Require all local gulp tasks. | ||
*/ | ||
@@ -8,0 +8,0 @@ requireDir('./tasks'); |
{ | ||
"name": "popsicle", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "Simple HTTP requests for node and the browser", | ||
@@ -5,0 +5,0 @@ "main": "popsicle.js", |
253
popsicle.js
@@ -44,7 +44,16 @@ (function (root) { | ||
* | ||
* @param {Object} obj | ||
* @param {String} property | ||
* @param {String} callback | ||
* @return {Function} | ||
*/ | ||
function setProgress (property) { | ||
return function (num) { | ||
function setProgress (obj, property, callback) { | ||
var method = '_set' + property.charAt(0).toUpperCase() + property.slice(1); | ||
/** | ||
* Create the progress update method. | ||
* | ||
* @param {Number} num | ||
*/ | ||
obj[method] = function (num) { | ||
if (this[property] === num) { | ||
@@ -56,2 +65,4 @@ return; | ||
this[callback](); | ||
this._completed(); | ||
this._emitProgress(); | ||
@@ -62,24 +73,27 @@ }; | ||
/** | ||
* Calculate the length of the current request as a percent of the total. | ||
* Generate a random number between two digits. | ||
* | ||
* @param {Number} length | ||
* @param {Number} total | ||
* @param {Number} low | ||
* @param {Number} high | ||
* @return {Number} | ||
*/ | ||
function calc (length, total) { | ||
if (total == null) { | ||
return 0; | ||
} | ||
function between (low, high) { | ||
var diff = high - low; | ||
return total === length ? 1 : length / total; | ||
return Math.random() * diff + low; | ||
} | ||
/** | ||
* Return zero if the number is `Infinity`. | ||
* Calculate the percentage of a request. | ||
* | ||
* @param {Number} num | ||
* @param {Number} size | ||
* @param {Number} total | ||
* @return {Number} | ||
*/ | ||
function infinity (num) { | ||
return num === Infinity ? 0 : num; | ||
function calc (n, size, total) { | ||
if (isNaN(total)) { | ||
return n + ((1 - n) * between(0.1, 0.45)); | ||
} | ||
return Math.min(1, size / total); | ||
} | ||
@@ -106,2 +120,12 @@ | ||
/** | ||
* Turn a value into a number (avoid `null` becoming `0`). | ||
* | ||
* @param {String} str | ||
* @return {Number} | ||
*/ | ||
function num (str) { | ||
return str == null ? NaN : Number(str); | ||
} | ||
/** | ||
* Create a stream error instance. | ||
@@ -545,21 +569,18 @@ * | ||
// Override `Request.prototype.write` to track written data. | ||
request.write = function (data) { | ||
self._setRequestLength(self._requestLength + byteLength(data)); | ||
function onRequest () { | ||
var write = request.req.write; | ||
return write.apply(request, arguments); | ||
}; | ||
self.uploadTotal = num(request.headers['content-length']); | ||
function onRequestEnd () { | ||
self._setRequestTotal(self._requestLength); | ||
} | ||
// Override `Request.prototype.write` to track amount of sent data. | ||
request.req.write = function (data) { | ||
self._setUploadSize(self.uploadSize + byteLength(data)); | ||
function onRequest () { | ||
self._setRequestTotal(Number(request.headers['content-length']) || 0); | ||
request.req.on('finish', onRequestEnd); | ||
return write.apply(this, arguments); | ||
}; | ||
} | ||
function onResponse (response) { | ||
self._responseTotal = Number(response.headers['content-length']) || 0; | ||
self.downloadTotal = num(response.headers['content-length']); | ||
self._uploadFinished(); | ||
} | ||
@@ -569,9 +590,8 @@ | ||
// Data should always be a `Buffer` instance. | ||
self._setResponseLength(self._responseLength + data.length); | ||
self._setDownloadSize(self.downloadSize + data.length); | ||
} | ||
function onResponseEnd () { | ||
self._setResponseTotal(self._responseLength); | ||
removeListeners(); | ||
self._completed(); | ||
self._downloadFinished(); | ||
} | ||
@@ -584,3 +604,3 @@ | ||
request.removeListener('end', onResponseEnd); | ||
request.req.removeListener('finish', onRequestEnd); | ||
request.removeListener('error', removeListeners); | ||
} | ||
@@ -817,7 +837,6 @@ | ||
// Progress properties. | ||
this._requestTotal = null; | ||
this._requestLength = 0; | ||
this._responseTotal = null; | ||
this._responseLength = 0; | ||
// Progress state. | ||
this.uploaded = this.downloaded = this.completed = 0; | ||
this.uploadSize = this.downloadSize = 0; | ||
this.uploadTotal = this.downloadTotal = NaN; | ||
@@ -829,3 +848,2 @@ // Set request headers. | ||
this.aborted = false; | ||
this.completed = false; | ||
@@ -864,20 +882,2 @@ // Parse query strings already set. | ||
/** | ||
* Check how far the request has been uploaded. | ||
* | ||
* @return {Number} | ||
*/ | ||
Request.prototype.uploaded = function () { | ||
return calc(this._requestLength, this._requestTotal); | ||
}; | ||
/** | ||
* Check how far the request has been downloaded. | ||
* | ||
* @return {Number} | ||
*/ | ||
Request.prototype.downloaded = function () { | ||
return calc(this._responseLength, this._responseTotal); | ||
}; | ||
/** | ||
* Track request completion progress. | ||
@@ -901,13 +901,41 @@ * | ||
/** | ||
* Set various progress properties. | ||
* Set upload progress properties. | ||
* | ||
* @private | ||
* @param {Number} num | ||
* @type {Function} | ||
* @param {Number} num | ||
*/ | ||
Request.prototype._setRequestTotal = setProgress('_requestTotal'); | ||
Request.prototype._setRequestLength = setProgress('_requestLength'); | ||
Request.prototype._setResponseTotal = setProgress('_responseTotal'); | ||
Request.prototype._setResponseLength = setProgress('_responseLength'); | ||
setProgress(Request.prototype, 'uploadSize', '_uploaded'); | ||
setProgress(Request.prototype, 'downloadSize', '_downloaded'); | ||
/** | ||
* Calculate the uploaded percentage. | ||
*/ | ||
Request.prototype._uploaded = function () { | ||
var n = this.uploaded; | ||
var size = this.uploadSize; | ||
var total = this.uploadTotal; | ||
this.uploaded = calc(n, size, total); | ||
}; | ||
/** | ||
* Calculate the downloaded percentage. | ||
*/ | ||
Request.prototype._downloaded = function () { | ||
var n = this.downloaded; | ||
var size = this.downloadSize; | ||
var total = this.downloadTotal; | ||
this.downloaded = calc(n, size, total); | ||
}; | ||
/** | ||
* Update the completed percentage. | ||
*/ | ||
Request.prototype._completed = function () { | ||
this.completed = (this.uploaded + this.downloaded) / 2; | ||
}; | ||
/** | ||
* Emit a request progress event (upload or download). | ||
@@ -918,37 +946,48 @@ */ | ||
if (!fns) { | ||
if (!fns || this._error) { | ||
return; | ||
} | ||
var self = this; | ||
var aborted = this.aborted; | ||
var uploaded = this.uploaded(); | ||
var downloaded = this.downloaded(); | ||
var total = (infinity(uploaded) + infinity(downloaded)) / 2; | ||
function emitProgress () { | ||
try { | ||
fns.forEach(function (fn) { | ||
// Emit new progress object every iteration to avoid issues if a | ||
// function mutates the object. | ||
fn({ | ||
uploaded: uploaded, | ||
downloaded: downloaded, | ||
total: total, | ||
aborted: aborted | ||
}); | ||
}); | ||
} catch (e) { | ||
self._errored(e); | ||
try { | ||
for (var i = 0; i < fns.length; i++) { | ||
fns[i](this); | ||
} | ||
} catch (e) { | ||
this._errored(e); | ||
} | ||
}; | ||
emitProgress(); | ||
/** | ||
* Finished uploading. | ||
*/ | ||
Request.prototype._uploadFinished = function () { | ||
if (this.uploaded === 1) { | ||
return; | ||
} | ||
this.uploaded = 1; | ||
this.completed = 0.5; | ||
this._emitProgress(); | ||
}; | ||
/** | ||
* Complete the request. | ||
* Finished downloading. | ||
*/ | ||
Request.prototype._completed = function () { | ||
this.completed = true; | ||
Request.prototype._downloadFinished = function () { | ||
if (this.downloaded === 1) { | ||
return; | ||
} | ||
this.downloaded = 1; | ||
this.completed = 1; | ||
this._emitProgress(); | ||
}; | ||
/** | ||
* Tidy up after requests. | ||
*/ | ||
Request.prototype._finished = function () { | ||
this.completed = 1; | ||
delete this._progressFns; | ||
@@ -1020,11 +1059,10 @@ }; | ||
// Reset progress and complete progress events. | ||
this._requestTotal = 0; | ||
this._requestLength = 0; | ||
this._responseTotal = 0; | ||
this._responseLength = 0; | ||
// Set everything to be completed. | ||
this.downloaded = this.uploaded = this.completed = 1; | ||
// Emit a final progress event for listeners. | ||
this._emitProgress(); | ||
this._abort(); | ||
this._completed(); | ||
this._finished(); | ||
clearTimeout(this._timer); | ||
@@ -1204,5 +1242,8 @@ | ||
if (xhr.readyState === 2) { | ||
self._responseTotal = Number( | ||
xhr.getResponseHeader('Content-Length') | ||
) || 0; | ||
self.downloadTotal = num(xhr.getResponseHeader('Content-Length')); | ||
// Trigger upload finished after we get the response length. | ||
// Otherwise, it's possible this method will error and make the | ||
// `xhr` object invalid. | ||
self._uploadFinished(); | ||
} | ||
@@ -1214,3 +1255,3 @@ | ||
delete xhr.upload.onprogress; | ||
self._completed(); | ||
self._downloadFinished(); | ||
@@ -1231,4 +1272,2 @@ if (self._error) { | ||
self._setResponseTotal(self._responseLength); | ||
var res = new Response({ | ||
@@ -1248,4 +1287,7 @@ raw: xhr, | ||
xhr.onprogress = function (e) { | ||
self._setResponseTotal(e.total); | ||
self._setResponseLength(e.loaded); | ||
if (e.lengthComputable) { | ||
self.downloadTotal = e.total; | ||
} | ||
self._setDownloadSize(e.loaded); | ||
}; | ||
@@ -1257,8 +1299,11 @@ | ||
self._setRequestTotal(0); | ||
self._setRequestLength(0); | ||
self.uploadTotal = 0; | ||
self._setUploadSize(0); | ||
} else { | ||
xhr.upload.onprogress = function (e) { | ||
self._setRequestTotal(e.total); | ||
self._setRequestLength(e.loaded); | ||
if (e.lengthComputable) { | ||
self.uploadTotal = e.total; | ||
} | ||
self._setUploadSize(e.loaded); | ||
}; | ||
@@ -1265,0 +1310,0 @@ } |
@@ -126,20 +126,24 @@ # ![Popsicle](https://cdn.rawgit.com/blakeembrey/popsicle/master/logo.svg) | ||
`Request#uploaded` can be used to check the upload progress (between `0` and `1`) of the current request. If the request is being streamed (node), the length may incorrectly return `Infinity` to signify the number can't be calculated. | ||
* **req.uploadSize** Current upload size in bytes | ||
* **req.uploadTotal** Total upload size in bytes | ||
* **req.uploaded** Total uploaded as a percentage | ||
* **req.downloadSize** Current download size in bytes | ||
* **req.downloadTotal** Total download size in bytes | ||
* **req.downloaded** Total downloaded as a percentage | ||
* **req.completed** Total uploaded and downloaded as a percentage | ||
`Request#downloaded` can be used to check the download progress (between `0` and `1`) of the current request instance. If the response did not respond with a `Content-Length` header this can not be calculated properly and will return `Infinity`. | ||
All percentage properties (`req.uploaded`, `req.downloaded`, `req.completed`) will be a number between `0` and `1`. When the total size is unknown (no `Content-Length` header), the percentage will automatically increment on each chunk of data returned (this will not be accurate). | ||
`Request#progress(fn)` can be used to register a progress event listener. It'll emit on upload and download progress events, which makes it useful for SPA progress bars. The function is called with an object (`{ uploaded: 0, downloaded: 0, total: 0, aborted: false }`). | ||
```javascript | ||
var req = request('http://example.com'); | ||
req.uploaded(); //=> 0 | ||
req.downloaded(); //=> 0 | ||
req.uploaded; //=> 0 | ||
req.downloaded; //=> 0 | ||
req.progress(function (e) { | ||
console.log(e); //=> { uploaded: 1, downloaded: 0, total: 0.5, aborted: false } | ||
console.log(e); //=> { uploaded: 1, downloaded: 0, completed: 0.5, aborted: false } | ||
}); | ||
req.then(function (res) { | ||
console.log(req.downloaded()); //=> 1 | ||
console.log(req.downloaded); //=> 1 | ||
}); | ||
@@ -146,0 +150,0 @@ ``` |
@@ -1,5 +0,1 @@ | ||
var isPhantom = typeof window !== 'undefined' && | ||
window.outerWidth === 0 && | ||
window.outerHeight === 0; | ||
var REMOTE_URL = 'http://localhost:4567'; | ||
@@ -349,7 +345,7 @@ | ||
// Before the request has started. | ||
expect(req.downloaded()).to.equal(0); | ||
expect(req.downloaded).to.equal(0); | ||
// Check halfway into the response. | ||
setTimeout(function () { | ||
assert = req.downloaded() === 0.5; | ||
assert = req.downloaded === 0.5; | ||
}, 100); | ||
@@ -364,3 +360,3 @@ | ||
expect(req.downloaded()).to.equal(1); | ||
expect(req.downloaded).to.equal(1); | ||
}); | ||
@@ -382,13 +378,6 @@ }); | ||
req.progress(function (e) { | ||
// Fix for PhantomJS tests (doesn't return `Content-Length` header). | ||
if (isPhantom && e.downloaded === 0 && expected === 1) { | ||
console.warn('PhantomJS does not support "Content-Length" header'); | ||
return; | ||
} | ||
expect(e.total).to.equal(expected); | ||
asserted += 1; | ||
expected += 0.5; | ||
expect(e.completed).to.equal(expected); | ||
}); | ||
@@ -398,3 +387,3 @@ | ||
.then(function (res) { | ||
expect(asserted).to.equal(3); | ||
expect(asserted).to.equal(2); | ||
expect(res.body).to.deep.equal(EXAMPLE_BODY); | ||
@@ -430,3 +419,3 @@ }); | ||
req.progress(function (e) { | ||
expect(e.total).to.equal(1); | ||
expect(e.completed).to.equal(1); | ||
expect(e.aborted).to.be.true; | ||
@@ -433,0 +422,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
68943
1866
272