summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Firebaugh <john.firebaugh@gmail.com>2017-07-19 15:20:53 -0700
committerJohn Firebaugh <john.firebaugh@gmail.com>2017-07-24 15:19:15 -0700
commit264ca961bddba621418303bf193dd68d9ce29791 (patch)
treec5bb57815b4245c1f5590cc3084d3c1684c48515
parented29dfce436cb68c91fa2a1bdaa995124121c218 (diff)
downloadqtlocation-mapboxgl-264ca961bddba621418303bf193dd68d9ce29791.tar.gz
[node] Protect against more badly behaved request implementations
-rw-r--r--platform/node/index.js29
-rw-r--r--platform/node/test/js/request.test.js48
2 files changed, 70 insertions, 7 deletions
diff --git a/platform/node/index.js b/platform/node/index.js
index e36a47943d..5944a0a27d 100644
--- a/platform/node/index.js
+++ b/platform/node/index.js
@@ -19,14 +19,29 @@ var Map = function(options) {
return new constructor(Object.assign(options, {
request: function(req) {
- request(req, function() {
+ // Protect against `request` implementations that call the callback synchronously,
+ // call it multiple times, or throw exceptions.
+ // http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
+
+ var responded = false;
+ var callback = function() {
var args = arguments;
- // Protect against `request` implementations that call the callback synchronously.
- // http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony
- process.nextTick(function() {
- req.respond.apply(req, args);
- });
- });
+ if (!responded) {
+ responded = true;
+ process.nextTick(function() {
+ req.respond.apply(req, args);
+ });
+ } else {
+ console.warn('request function responded multiple times; it should call the callback only once');
+ }
+ };
+
+ try {
+ request(req, callback);
+ } catch (e) {
+ console.warn('request function threw an exception; it should call the callback with an error instead');
+ callback(e);
+ }
}
}));
};
diff --git a/platform/node/test/js/request.test.js b/platform/node/test/js/request.test.js
index 9715633e39..ec2b084f4e 100644
--- a/platform/node/test/js/request.test.js
+++ b/platform/node/test/js/request.test.js
@@ -66,3 +66,51 @@ var test = require('tape');
});
});
});
+
+test(`render reports an error if the request function throws an exception`, function(t) {
+ var map = new mbgl.Map({
+ request: function() {
+ throw new Error('message');
+ }
+ });
+ map.load(mockfs.style_vector);
+ map.render({ zoom: 16 }, function(err, pixels) {
+ t.assert(err);
+ t.assert(/message/.test(err.message));
+ t.assert(!pixels);
+ t.end();
+ });
+});
+
+test(`render ignores request functions throwing an exception after calling the callback`, function(t) {
+ var map = new mbgl.Map({
+ request: function(req, callback) {
+ var data = mockfs.dataForRequest(req);
+ callback(null, { data: data });
+ throw new Error('message');
+ }
+ });
+ map.load(mockfs.style_vector);
+ map.render({ zoom: 16 }, function(err, pixels) {
+ t.error(err);
+ t.assert(pixels);
+ t.end();
+ });
+});
+
+test(`render ignores request functions calling the callback a second time`, function(t) {
+ var map = new mbgl.Map({
+ request: function(req, callback) {
+ var data = mockfs.dataForRequest(req);
+ callback(null, { data: data });
+ callback(null, { data: data });
+ }
+ });
+ map.load(mockfs.style_vector);
+ map.render({ zoom: 16 }, function(err, pixels) {
+ t.error(err);
+ t.assert(pixels);
+ t.end();
+ });
+});
+