diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e88be04cc..f7294379e 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -11,9 +11,8 @@ rendering the map data as well as many sorts of general DOM manipulation tasks for which jQuery would often be used. Notable features of d3 that are used by iD include -[d3.request](https://github.com/d3/d3/blob/master/API.md#requests-d3-request), which is -used to make the API requests to download data from openstreetmap.org and save -changes; +[d3.fetch](https://github.com/d3/d3/blob/master/API.md#fetches-d3-fetch), which is +used to make the API requests to download data from openstreetmap.org and save changes; [d3.dispatch](https://github.com/d3/d3/blob/master/API.md#dispatches-d3-dispatch), which provides a callback-based [Observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) between different diff --git a/modules/core/context.js b/modules/core/context.js index 08e9b02dc..9022c6351 100644 --- a/modules/core/context.js +++ b/modules/core/context.js @@ -1,7 +1,7 @@ import _debounce from 'lodash-es/debounce'; import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { select as d3_select } from 'd3-selection'; import { t, currentLocale, addTranslation, setLocale } from '../util/locale'; @@ -434,16 +434,16 @@ export function coreContext() { context.loadLocale = function(callback) { if (locale && locale !== 'en' && dataLocales.hasOwnProperty(locale)) { localePath = localePath || context.asset('locales/' + locale + '.json'); - d3_json(localePath, function(err, result) { - if (!err) { + d3_json(localePath) + .then(function(result) { addTranslation(locale, result[locale]); setLocale(locale); utilDetect(true); - } - if (callback) { - callback(err); - } - }); + if (callback) callback(); + }) + .catch(function(err) { + if (callback) callback(err); + }); } else { if (locale) { setLocale(locale); @@ -520,13 +520,16 @@ export function coreContext() { if (services.maprules && utilStringQs(window.location.hash).maprules) { var maprules = utilStringQs(window.location.hash).maprules; - d3_json(maprules, function (err, mapcss) { - if (err) return; - services.maprules.init(); - mapcss.forEach(function(mapcssSelector) { - return services.maprules.addRule(mapcssSelector); + d3_json(maprules) + .then(function(mapcss) { + services.maprules.init(); + mapcss.forEach(function(mapcssSelector) { + return services.maprules.addRule(mapcssSelector); + }); + }) + .catch(function() { + /* ignore */ }); - }); } map = rendererMap(context); diff --git a/modules/presets/index.js b/modules/presets/index.js index 35d77bcb6..a0611263b 100644 --- a/modules/presets/index.js +++ b/modules/presets/index.js @@ -1,5 +1,5 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { data } from '../../data/index'; import { presetCategory } from './category'; @@ -251,15 +251,17 @@ export function presetIndex(context) { all.fromExternal = function(external, done) { all.reset(); - d3_json(external, function(err, externalPresets) { - if (err) { + d3_json(external) + .then(function(externalPresets) { + all.build(data.presets, false); // make default presets hidden to begin + all.build(externalPresets, true); // make the external visible + }) + .catch(function() { all.init(); - } else { - all.build(data.presets, false); // make default presets hidden to begin - all.build(externalPresets, true); // make the external visible - } - done(all); - }); + }) + .finally(function() { + done(all); + }); }; all.field = function(id) { diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index 0ee86e797..affbb6c37 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -1,9 +1,5 @@ -import { - geoArea as d3_geoArea, - geoMercatorRaw as d3_geoMercatorRaw -} from 'd3-geo'; - -import { json as d3_json } from 'd3-request'; +import { geoArea as d3_geoArea, geoMercatorRaw as d3_geoMercatorRaw } from 'd3-geo'; +import { json as d3_json } from 'd3-fetch'; import { t } from '../util/locale'; import { geoExtent, geoSphericalDistance } from '../geo'; @@ -218,27 +214,29 @@ rendererBackgroundSource.Bing = function(data, dispatch) { var bing = rendererBackgroundSource(data); var key = 'Arzdiw4nlOJzRwOz__qailc8NiR31Tt51dN2D7cm57NrnceZnCpgOkmJhNpGoppU'; // Same as P2 and JOSM - var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + - key; + var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key; var cache = {}; var inflight = {}; var providers = []; - d3_json(url, function(err, json) { - if (err) return; - providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) { - return { - attribution: provider.attribution, - areas: provider.coverageAreas.map(function(area) { - return { - zoom: [area.zoomMin, area.zoomMax], - extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) - }; - }) - }; + d3_json(url) + .then(function(json) { + providers = json.resourceSets[0].resources[0].imageryProviders.map(function(provider) { + return { + attribution: provider.attribution, + areas: provider.coverageAreas.map(function(area) { + return { + zoom: [area.zoomMin, area.zoomMax], + extent: geoExtent([area.bbox[1], area.bbox[0]], [area.bbox[3], area.bbox[2]]) + }; + }) + }; + }); + dispatch.call('change'); + }) + .catch(function() { + /* ignore */ }); - dispatch.call('change'); - }); bing.copyrightNotices = function(zoom, extent) { @@ -256,34 +254,28 @@ rendererBackgroundSource.Bing = function(data, dispatch) { bing.getMetadata = function(center, tileCoord, callback) { - var tileId = tileCoord.slice(0, 3).join('/'); + var tileID = tileCoord.slice(0, 3).join('/'); var zoom = Math.min(tileCoord[2], 21); var centerPoint = center[1] + ',' + center[0]; // lat,lng var url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + '?zl=' + zoom + '&key=' + key; - if (inflight[tileId]) return; + if (inflight[tileID]) return; - if (!cache[tileId]) { - cache[tileId] = {}; + if (!cache[tileID]) { + cache[tileID] = {}; } - if (cache[tileId] && cache[tileId].metadata) { - return callback(null, cache[tileId].metadata); + if (cache[tileID] && cache[tileID].metadata) { + return callback(null, cache[tileID].metadata); } - inflight[tileId] = true; - d3_json(url, function(error, result) { - delete inflight[tileId]; - - var err; - if (error) { - err = error; - } else if (!result && 'Unknown Error') { - err = result.errorDetails; - } - if (err) { - return callback(err); - } else { + inflight[tileID] = true; + d3_json(url) + .then(function(result) { + delete inflight[tileID]; + if (!result) { + throw new Error('Unknown Error'); + } var vintage = { start: localeDateString(result.resourceSets[0].resources[0].vintageStart), end: localeDateString(result.resourceSets[0].resources[0].vintageEnd) @@ -291,10 +283,13 @@ rendererBackgroundSource.Bing = function(data, dispatch) { vintage.range = vintageRange(vintage); var metadata = { vintage: vintage }; - cache[tileId].metadata = metadata; - return callback(null, metadata); - } - }); + cache[tileID].metadata = metadata; + if (callback) callback(null, metadata); + }) + .catch(function(err) { + delete inflight[tileID]; + if (callback) callback(err); + }); }; @@ -338,25 +333,31 @@ rendererBackgroundSource.Esri = function(data) { var tilemapUrl = dummyUrl.replace(/tile\/[0-9]+\/[0-9]+\/[0-9]+\?blankTile=false/, 'tilemap') + '/' + z + '/' + y + '/' + x + '/8/8'; // make the request and introspect the response from the tilemap server - d3_json(tilemapUrl, function (err, tilemap) { - if (err || !tilemap) return; - - var hasTiles = true; - for (var i = 0; i < tilemap.data.length; i++) { - // 0 means an individual tile in the grid doesn't exist - if (!tilemap.data[i]) { - hasTiles = false; - break; + d3_json(tilemapUrl) + .then(function(tilemap) { + if (!tilemap) { + throw new Error('Unknown Error'); + } + var hasTiles = true; + for (var i = 0; i < tilemap.data.length; i++) { + // 0 means an individual tile in the grid doesn't exist + if (!tilemap.data[i]) { + hasTiles = false; + break; + } } - } - // if any tiles are missing at level 20 we restrict maxZoom to 19 - esri.zoomExtent[1] = (hasTiles ? 22 : 19); - }); + // if any tiles are missing at level 20 we restrict maxZoom to 19 + esri.zoomExtent[1] = (hasTiles ? 22 : 19); + }) + .catch(function() { + /* ignore */ + }); }; + esri.getMetadata = function(center, tileCoord, callback) { - var tileId = tileCoord.slice(0, 3).join('/'); + var tileID = tileCoord.slice(0, 3).join('/'); var zoom = Math.min(tileCoord[2], esri.zoomExtent[1]); var centerPoint = center[0] + ',' + center[1]; // long, lat (as it should be) var unknown = t('info_panels.background.unknown'); @@ -364,7 +365,7 @@ rendererBackgroundSource.Esri = function(data) { var vintage = {}; var metadata = {}; - if (inflight[tileId]) return; + if (inflight[tileID]) return; switch (true) { case (zoom >= 20 && esri.id === 'EsriWorldImageryClarity'): @@ -393,11 +394,11 @@ rendererBackgroundSource.Esri = function(data) { url += metadataLayer + '/query?returnGeometry=false&geometry=' + centerPoint + '&inSR=4326&geometryType=esriGeometryPoint&outFields=*&f=json'; - if (!cache[tileId]) { - cache[tileId] = {}; + if (!cache[tileID]) { + cache[tileID] = {}; } - if (cache[tileId] && cache[tileId].metadata) { - return callback(null, cache[tileId].metadata); + if (cache[tileID] && cache[tileID].metadata) { + return callback(null, cache[tileID].metadata); } // accurate metadata is only available >= 13 @@ -418,24 +419,18 @@ rendererBackgroundSource.Esri = function(data) { callback(null, metadata); } else { - inflight[tileId] = true; - d3_json(url, function(error, result) { - delete inflight[tileId]; + inflight[tileID] = true; + d3_json(url) + .then(function(result) { + delete inflight[tileID]; + if (!result) { + throw new Error('Unknown Error'); + } else if (result.features && result.features.length < 1) { + throw new Error('No Results'); + } else if (result.error && result.error.message) { + throw new Error(result.error.message); + } - var err; - if (error) { - err = error; - } else if (!result) { - err = 'Unknown Error'; - } else if (result.features && result.features.length < 1) { - err = 'No Results'; - } else if (result.error && result.error.message) { - err = result.error.message; - } - - if (err) { - return callback(err); - } else { // pass through the discrete capture date from metadata var captureDate = localeDateString(result.features[0].attributes.SRC_DATE2); vintage = { @@ -459,10 +454,13 @@ rendererBackgroundSource.Esri = function(data) { metadata.accuracy += ' m'; } - cache[tileId].metadata = metadata; - return callback(null, metadata); - } - }); + cache[tileID].metadata = metadata; + if (callback) callback(null, metadata); + }) + .catch(function(error) { + delete inflight[tileID]; + if (callback) callback(error); + }); } diff --git a/modules/services/improveOSM.js b/modules/services/improveOSM.js index a4e714238..26cc86a55 100644 --- a/modules/services/improveOSM.js +++ b/modules/services/improveOSM.js @@ -1,8 +1,7 @@ import rbush from 'rbush'; import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { json as d3_json } from 'd3-request'; -import { request as d3_request } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { geoExtent, geoVecAdd, geoVecScale } from '../geo'; import { qaError } from '../osm'; @@ -24,9 +23,9 @@ var _impOsmUrls = { }; function abortRequest(i) { - Object.values(i).forEach(function(v) { - if (v) { - v.abort(); + Object.values(i).forEach(function(controller) { + if (controller) { + controller.abort(); } }); } @@ -177,12 +176,16 @@ export default { ); var url = v + '/search?' + utilQsString(kParams); - requests[k] = d3_json(url, - function(err, data) { - delete _erCache.inflightTile[tile.id]; + var controller = new AbortController(); + requests[k] = controller; - if (err) return; - _erCache.loadedTile[tile.id] = true; + d3_json(url, { signal: controller.signal }) + .then(function(data) { + delete _erCache.inflightTile[tile.id][k]; + if (!Object.keys(_erCache.inflightTile[tile.id]).length) { + delete _erCache.inflightTile[tile.id]; + _erCache.loadedTile[tile.id] = true; + } // Road segments at high zoom == oneways if (data.roadSegments) { @@ -317,20 +320,29 @@ export default { _erCache.data[d.id] = d; _erCache.rtree.insert(encodeErrorRtree(d)); + dispatch.call('loaded'); }); } - } - ); + }) + .catch(function() { + delete _erCache.inflightTile[tile.id][k]; + if (!Object.keys(_erCache.inflightTile[tile.id]).length) { + delete _erCache.inflightTile[tile.id]; + _erCache.loadedTile[tile.id] = true; + } + }); }); _erCache.inflightTile[tile.id] = requests; - dispatch.call('loaded'); }); }, getComments: function(d, callback) { // If comments already retrieved no need to do so again - if (d.comments !== undefined) { return callback({}, d); } + if (d.comments !== undefined) { + if (callback) callback({}, d); + return; + } var key = d.error_key; var qParams = {}; @@ -347,15 +359,16 @@ export default { var url = _impOsmUrls[key] + '/retrieveComments?' + utilQsString(qParams); var that = this; - d3_json(url, function(err, data) { - // comments are served newest to oldest - var comments = data.comments ? data.comments.reverse() : []; - - that.replaceError(d.update({ - comments: comments - })); - return callback(err, d); - }); + d3_json(url) + .then(function(data) { + // comments are served newest to oldest + var comments = data.comments ? data.comments.reverse() : []; + that.replaceError(d.update({ comments: comments })); + if (callback) callback(null, d); + }) + .catch(function(err) { + if (callback) callback(err); + }); }, postUpdate: function(d, callback) { @@ -399,13 +412,18 @@ export default { payload.text = d.newComment; } - _erCache.inflightPost[d.id] = d3_request(url) - .header('Content-Type', 'application/json') - .post(JSON.stringify(payload), function(err) { - delete _erCache.inflightPost[d.id]; + var controller = new AbortController(); + _erCache.inflightPost[d.id] = controller; - // Unsuccessful response status, keep issue open - if (err.status !== 200) { return callback(err, d); } + var options = { + method: 'POST', + signal: controller.signal, + body: JSON.stringify(payload) + }; + + d3_json(url, options) + .then(function() { + delete _erCache.inflightPost[d.id]; // Just a comment, update error in cache if (d.newStatus === undefined) { @@ -424,19 +442,22 @@ export default { })); } else { that.removeError(d); - if (d.newStatus === 'SOLVED') { // No pretty identifier, so we just use coordinates var closedID = d.loc[1].toFixed(5) + '/' + d.loc[0].toFixed(5); _erCache.closed[key + ':' + closedID] = true; } } - - return callback(err, d); + if (callback) callback(null, d); + }) + .catch(function(err) { + delete _erCache.inflightPost[d.id]; + if (callback) callback(err); }); } }, + // get all cached errors covering the viewport getErrors: function(projection) { var viewport = projection.clipExtent(); diff --git a/modules/services/keepRight.js b/modules/services/keepRight.js index 6c40b3dcf..9c297d7b3 100644 --- a/modules/services/keepRight.js +++ b/modules/services/keepRight.js @@ -1,8 +1,7 @@ import rbush from 'rbush'; import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { json as d3_json } from 'd3-request'; -import { request as d3_request } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { geoExtent, geoVecAdd } from '../geo'; import { qaError } from '../osm'; @@ -30,9 +29,9 @@ var _krRuleset = [ ]; -function abortRequest(i) { - if (i) { - i.abort(); +function abortRequest(controller) { + if (controller) { + controller.abort(); } } @@ -308,14 +307,16 @@ export default { var params = Object.assign({}, options, { left: rect[0], bottom: rect[3], right: rect[2], top: rect[1] }); var url = _krUrlRoot + 'export.php?' + utilQsString(params) + '&ch=' + rules; - _krCache.inflightTile[tile.id] = d3_json(url, - function(err, data) { + var controller = new AbortController(); + _krCache.inflightTile[tile.id] = controller; + + d3_json(url, { signal: controller.signal }) + .then(function(data) { delete _krCache.inflightTile[tile.id]; - - if (err) return; _krCache.loadedTile[tile.id] = true; - - if (!data.features || !data.features.length) return; + if (!data || !data.features || !data.features.length) { + throw new Error('No Data'); + } data.features.forEach(function(feature) { var loc = feature.geometry.coordinates; @@ -396,8 +397,12 @@ export default { }); dispatch.call('loaded'); - } - ); + }) + .catch(function() { + delete _krCache.inflightTile[tile.id]; + _krCache.loadedTile[tile.id] = true; + }); + }); }, @@ -420,9 +425,16 @@ export default { // NOTE: This throws a CORS err, but it seems successful. // We don't care too much about the response, so this is fine. var url = _krUrlRoot + 'comment.php?' + utilQsString(params); - _krCache.inflightPost[d.id] = d3_request(url) - .post(function(err) { + + var controller = new AbortController(); + _krCache.inflightPost[d.id] = controller; + + fetch(url, { method: 'POST', signal: controller.signal }) + .then(function(response) { delete _krCache.inflightPost[d.id]; + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } if (d.state === 'ignore') { // ignore permanently (false positive) that.removeError(d); @@ -439,9 +451,12 @@ export default { })); } - return callback(err, d); + if (callback) callback(null, d); + }) + .catch(function(err) { + delete _krCache.inflightPost[d.id]; + if (callback) callback(err); }); - }, diff --git a/modules/services/mapillary.js b/modules/services/mapillary.js index 1369b0e30..101728703 100644 --- a/modules/services/mapillary.js +++ b/modules/services/mapillary.js @@ -1,10 +1,6 @@ /* global Mapillary:false */ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { request as d3_request } from 'd3-request'; -import { - select as d3_select, - selectAll as d3_selectAll -} from 'd3-selection'; +import { select as d3_select, selectAll as d3_selectAll } from 'd3-selection'; import rbush from 'rbush'; @@ -28,8 +24,8 @@ var _mlySelectedImage; var _mlyViewer; -function abortRequest(i) { - i.abort(); +function abortRequest(controller) { + controller.abort(); } @@ -80,22 +76,36 @@ function loadNextTilePage(which, currZoom, url, tile) { var id = tile.id + ',' + String(nextPage); if (cache.loaded[id] || cache.inflight[id]) return; - cache.inflight[id] = d3_request(nextURL) - .mimeType('application/json') - .response(function(xhr) { - var linkHeader = xhr.getResponseHeader('Link'); + + var controller = new AbortController(); + cache.inflight[id] = controller; + + var options = { + method: 'GET', + signal: controller.signal, + headers: { 'Content-Type': 'application/json' } + }; + + fetch(nextURL, options) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } + var linkHeader = response.headers.Link; if (linkHeader) { - var pagination = parsePagination(xhr.getResponseHeader('Link')); + var pagination = parsePagination(linkHeader); if (pagination.next) { cache.nextURL[tile.id] = pagination.next; } } - return JSON.parse(xhr.responseText); + return response.json(); }) - .get(function(err, data) { + .then(function(data) { cache.loaded[id] = true; delete cache.inflight[id]; - if (err || !data.features || !data.features.length) return; + if (!data || !data.features || !data.features.length) { + throw new Error('No Data'); + } var features = data.features.map(function(feature) { var loc = feature.geometry.coordinates; @@ -182,6 +192,10 @@ function loadNextTilePage(which, currZoom, url, tile) { } else { cache.nextPage[tile.id] = Infinity; // no more pages to load } + }) + .catch(function() { + cache.loaded[id] = true; + delete cache.inflight[id]; }); } diff --git a/modules/services/nominatim.js b/modules/services/nominatim.js index 37130e196..5d98c9176 100644 --- a/modules/services/nominatim.js +++ b/modules/services/nominatim.js @@ -1,4 +1,4 @@ -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import rbush from 'rbush'; import { geoExtent } from '../geo'; @@ -18,7 +18,7 @@ export default { }, reset: function() { - Object.values(_inflight).forEach(function(req) { req.abort(); }); + Object.values(_inflight).forEach(function(controller) { controller.abort(); }); _inflight = {}; _nominatimCache = rbush(); }, @@ -37,45 +37,62 @@ export default { }, - reverse: function (location, callback) { + reverse: function (loc, callback) { var cached = _nominatimCache.search( - { minX: location[0], minY: location[1], maxX: location[0], maxY: location[1] } + { minX: loc[0], minY: loc[1], maxX: loc[0], maxY: loc[1] } ); if (cached.length > 0) { - return callback(null, cached[0].data); + if (callback) callback(null, cached[0].data); + return; } - var params = { zoom: 13, format: 'json', addressdetails: 1, lat: location[1], lon: location[0] }; + var params = { zoom: 13, format: 'json', addressdetails: 1, lat: loc[1], lon: loc[0] }; var url = apibase + 'reverse?' + utilQsString(params); + if (_inflight[url]) return; + var controller = new AbortController(); + _inflight[url] = controller; - _inflight[url] = d3_json(url, function(err, result) { - delete _inflight[url]; - - if (err) { - return callback(err); - } else if (result && result.error) { - return callback(result.error); - } - - var extent = geoExtent(location).padByMeters(200); - _nominatimCache.insert(Object.assign(extent.bbox(), {data: result})); - - callback(null, result); - }); + d3_json(url, { signal: controller.signal }) + .then(function(result) { + delete _inflight[url]; + if (result && result.error) { + throw new Error(result.error); + } + var extent = geoExtent(loc).padByMeters(200); + _nominatimCache.insert(Object.assign(extent.bbox(), {data: result})); + if (callback) callback(null, result); + }) + .catch(function(err) { + delete _inflight[url]; + if (err.name === 'AbortError') return; + if (callback) callback(err); + }); }, search: function (val, callback) { var searchVal = encodeURIComponent(val); var url = apibase + 'search/' + searchVal + '?limit=10&format=json'; - if (_inflight[url]) return; - _inflight[url] = d3_json(url, function(err, result) { - delete _inflight[url]; - callback(err, result); - }); + if (_inflight[url]) return; + var controller = new AbortController(); + _inflight[url] = controller; + + d3_json(url, { signal: controller.signal }) + .then(function(result) { + delete _inflight[url]; + if (result && result.error) { + throw new Error(result.error); + } + if (callback) callback(null, result); + }) + .catch(function(err) { + delete _inflight[url]; + if (err.name === 'AbortError') return; + if (callback) callback(err); + }); } }; diff --git a/modules/services/openstreetcam.js b/modules/services/openstreetcam.js index 58c1f4225..366632047 100644 --- a/modules/services/openstreetcam.js +++ b/modules/services/openstreetcam.js @@ -1,16 +1,7 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { request as d3_request } from 'd3-request'; - -import { - event as d3_event, - select as d3_select, - selectAll as d3_selectAll -} from 'd3-selection'; - -import { - zoom as d3_zoom, - zoomIdentity as d3_zoomIdentity -} from 'd3-zoom'; +import { json as d3_json } from 'd3-fetch'; +import { event as d3_event, select as d3_select, selectAll as d3_selectAll } from 'd3-selection'; +import { zoom as d3_zoom, zoomIdentity as d3_zoomIdentity } from 'd3-zoom'; import rbush from 'rbush'; @@ -33,8 +24,8 @@ var _oscCache; var _oscSelectedImage; -function abortRequest(i) { - i.abort(); +function abortRequest(controller) { + controller.abort(); } @@ -86,14 +77,23 @@ function loadNextTilePage(which, currZoom, url, tile) { var id = tile.id + ',' + String(nextPage); if (cache.loaded[id] || cache.inflight[id]) return; - cache.inflight[id] = d3_request(url) - .mimeType('application/json') - .header('Content-type', 'application/x-www-form-urlencoded') - .response(function(xhr) { return JSON.parse(xhr.responseText); }) - .post(params, function(err, data) { + var controller = new AbortController(); + cache.inflight[id] = controller; + + var options = { + method: 'POST', + signal: controller.signal, + body: params, + headers: { 'Content-Type': 'application/x-www-form-urlencoded' } + }; + + d3_json(url, options) + .then(function(data) { cache.loaded[id] = true; delete cache.inflight[id]; - if (err || !data.currentPageItems || !data.currentPageItems.length) return; + if (!data || !data.currentPageItems || !data.currentPageItems.length) { + throw new Error('No Data'); + } function localeDateString(s) { if (!s) return null; @@ -146,6 +146,10 @@ function loadNextTilePage(which, currZoom, url, tile) { } else { cache.nextPage[tile.id] = Infinity; // no more pages to load } + }) + .catch(function() { + cache.loaded[id] = true; + delete cache.inflight[id]; }); } diff --git a/modules/services/osm.js b/modules/services/osm.js index 747e747a0..0c8c7c5a1 100644 --- a/modules/services/osm.js +++ b/modules/services/osm.js @@ -1,7 +1,7 @@ import _throttle from 'lodash-es/throttle'; import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { xml as d3_xml } from 'd3-request'; +import { xml as d3_xml } from 'd3-fetch'; import osmAuth from 'osm-auth'; import rbush from 'rbush'; @@ -51,9 +51,9 @@ function authDone() { } -function abortRequest(i) { - if (i) { - i.abort(); +function abortRequest(controllerOrXHR) { + if (controllerOrXHR) { + controllerOrXHR.abort(); } } @@ -468,7 +468,16 @@ export default { return oauth.xhr({ method: 'GET', path: path }, done); } else { var url = urlroot + path; - return d3_xml(url).get(done); + var controller = new AbortController(); + d3_xml(url, { signal: controller.signal }) + .then(function(data) { + done(null, data); + }) + .catch(function(err) { + if (err.name === 'AbortError') return; + done(err); + }); + return controller; } }, @@ -743,9 +752,11 @@ export default { // Fetch the status of the OSM API // GET /api/capabilities status: function(callback) { - d3_xml(urlroot + '/api/capabilities').get( - wrapcb(this, done, _connectionID) - ); + var url = urlroot + '/api/capabilities'; + var errback = wrapcb(this, done, _connectionID); + d3_xml(url) + .then(function(data) { errback(null, data); }) + .catch(function(err) { errback(err); }); function done(err, xml) { if (err) { return callback(err); } diff --git a/modules/services/osm_wikibase.js b/modules/services/osm_wikibase.js index 05504559a..edcc91df8 100644 --- a/modules/services/osm_wikibase.js +++ b/modules/services/osm_wikibase.js @@ -1,6 +1,6 @@ import _debounce from 'lodash-es/debounce'; -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { utilDetect } from '../util/detect'; import { utilQsString } from '../util'; @@ -16,11 +16,19 @@ var debouncedRequest = _debounce(request, 500, { leading: false }); function request(url, callback) { if (_inflight[url]) return; + var controller = new AbortController(); + _inflight[url] = controller; - _inflight[url] = d3_json(url, function (err, data) { - delete _inflight[url]; - callback(err, data); - }); + d3_json(url, { signal: controller.signal }) + .then(function(result) { + delete _inflight[url]; + if (callback) callback(null, result); + }) + .catch(function(err) { + delete _inflight[url]; + if (err.name === 'AbortError') return; + if (callback) callback(err); + }); } @@ -50,7 +58,7 @@ export default { reset: function() { - Object.values(_inflight).forEach(function(req) { req.abort(); }); + Object.values(_inflight).forEach(function(controller) { controller.abort(); }); _inflight = {}; }, diff --git a/modules/services/taginfo.js b/modules/services/taginfo.js index b1f849e49..a28c405c2 100644 --- a/modules/services/taginfo.js +++ b/modules/services/taginfo.js @@ -1,6 +1,6 @@ import _debounce from 'lodash-es/debounce'; -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { utilObjectOmit, utilQsString } from '../util'; import { currentLocale } from '../util/locale'; @@ -142,10 +142,19 @@ function request(url, params, exactMatch, callback, loaded) { if (checkCache(url, params, exactMatch, callback)) return; - _inflight[url] = d3_json(url, function (err, data) { - delete _inflight[url]; - loaded(err, data); - }); + var controller = new AbortController(); + _inflight[url] = controller; + + d3_json(url, { signal: controller.signal }) + .then(function(result) { + delete _inflight[url]; + if (loaded) loaded(null, result); + }) + .catch(function(err) { + delete _inflight[url]; + if (err.name === 'AbortError') return; + if (loaded) loaded(err); + }); } @@ -207,7 +216,7 @@ export default { reset: function() { - Object.values(_inflight).forEach(function(request) { request.abort(); }); + Object.values(_inflight).forEach(function(controller) { controller.abort(); }); _inflight = {}; }, diff --git a/modules/services/vector_tile.js b/modules/services/vector_tile.js index dfcbf970e..79928f6a6 100644 --- a/modules/services/vector_tile.js +++ b/modules/services/vector_tile.js @@ -1,5 +1,4 @@ import { dispatch as d3_dispatch } from 'd3-dispatch'; -import { request as d3_request } from 'd3-request'; import deepEqual from 'fast-deep-equal'; import turf_bboxClip from '@turf/bbox-clip'; @@ -17,8 +16,8 @@ var dispatch = d3_dispatch('loadedData'); var _vtCache; -function abortRequest(i) { - i.abort(); +function abortRequest(controller) { + controller.abort(); } @@ -105,12 +104,23 @@ function loadTile(source, tile) { return subdomains[(tile.xyz[0] + tile.xyz[1]) % subdomains.length]; }); - source.inflight[tile.id] = d3_request(url) - .responseType('arraybuffer') - .get(function(err, data) { + + var controller = new AbortController(); + source.inflight[tile.id] = controller; + + fetch(url, { signal: controller.signal }) + .then(function(response) { + if (!response.ok) { + throw new Error(response.status + ' ' + response.statusText); + } source.loaded[tile.id] = []; delete source.inflight[tile.id]; - if (err || !data) return; + return response.arrayBuffer(); + }) + .then(function(data) { + if (!data) { + throw new Error('No Data'); + } var z = tile.xyz[2]; if (!source.canMerge[z]) { @@ -119,6 +129,10 @@ function loadTile(source, tile) { source.loaded[tile.id] = vtToGeoJSON(data, tile, source.canMerge[z]); dispatch.call('loadedData'); + }) + .catch(function() { + source.loaded[tile.id] = []; + delete source.inflight[tile.id]; }); } diff --git a/modules/services/wikidata.js b/modules/services/wikidata.js index 0e88ec66b..81299a628 100644 --- a/modules/services/wikidata.js +++ b/modules/services/wikidata.js @@ -1,4 +1,4 @@ -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { utilArrayUniq, utilQsString } from '../util'; import { currentLocale } from '../util/locale'; @@ -19,11 +19,11 @@ export default { // Search for Wikidata items matching the query itemsForSearchQuery: function(query, callback) { if (!query) { - callback('No query', {}); + if (callback) callback('No query', {}); return; } - d3_json(apibase + utilQsString({ + var url = apibase + utilQsString({ action: 'wbsearchentities', format: 'json', formatversion: 2, @@ -32,30 +32,31 @@ export default { language: this.languagesToQuery()[0], limit: 10, origin: '*' - }), function(err, data) { - if (data && data.error) { - err = data.error; - } - if (err) { - callback(err, {}); - } else { - callback(null, data.search || {}); - } }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } + if (callback) callback(null, result.search || {}); + }) + .catch(function(err) { + if (callback) callback(err, {}); + }); }, - // Given a Wikipedia language and article title, return an array of - // corresponding Wikidata entities. + // Given a Wikipedia language and article title, + // return an array of corresponding Wikidata entities. itemsByTitle: function(lang, title, callback) { if (!title) { - callback('No title', {}); + if (callback) callback('No title', {}); return; } lang = lang || 'en'; - - d3_json(apibase + utilQsString({ + var url = apibase + utilQsString({ action: 'wbgetentities', format: 'json', formatversion: 2, @@ -63,18 +64,21 @@ export default { titles: title, languages: 'en', // shrink response by filtering to one language origin: '*' - }), function(err, data) { - if (data && data.error) { - err = data.error; - } - if (err) { - callback(err, {}); - } else { - callback(null, data.entities || {}); - } }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } + if (callback) callback(null, result.entities || {}); + }) + .catch(function(err) { + if (callback) callback(err, {}); + }); }, + languagesToQuery: function() { return utilArrayUniq([ currentLocale.toLowerCase(), @@ -83,19 +87,19 @@ export default { ]); }, + entityByQID: function(qid, callback) { if (!qid) { callback('No qid', {}); return; } if (_wikidataCache[qid]) { - callback(null, _wikidataCache[qid]); + if (callback) callback(null, _wikidataCache[qid]); return; } var langs = this.languagesToQuery(); - - d3_json(apibase + utilQsString({ + var url = apibase + utilQsString({ action: 'wbgetentities', format: 'json', formatversion: 2, @@ -105,17 +109,18 @@ export default { languages: langs.join('|'), languagefallback: 1, origin: '*' - }), function(err, data) { - if (data && data.error) { - err = data.error; - } - if (err) { - callback(err, {}); - } else { - _wikidataCache[qid] = data.entities[qid]; - callback(null, data.entities[qid] || {}); - } }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } + if (callback) callback(null, result.entities[qid] || {}); + }) + .catch(function(err) { + if (callback) callback(err, {}); + }); }, @@ -134,9 +139,7 @@ export default { // } // getDocs: function(params, callback) { - var langs = this.languagesToQuery(); - this.entityByQID(params.qid, function(err, entity) { if (err || !entity) { callback(err || 'No entity'); @@ -144,7 +147,6 @@ export default { } var i; - var description; if (entity.descriptions && Object.keys(entity.descriptions).length > 0) { description = entity.descriptions[Object.keys(entity.descriptions)[0]].value; diff --git a/modules/services/wikipedia.js b/modules/services/wikipedia.js index 980af8741..aa33052a8 100644 --- a/modules/services/wikipedia.js +++ b/modules/services/wikipedia.js @@ -1,4 +1,4 @@ -import { json as d3_json } from 'd3-request'; +import { json as d3_json } from 'd3-fetch'; import { utilQsString } from '../util'; @@ -13,12 +13,12 @@ export default { search: function(lang, query, callback) { if (!query) { - callback('', []); + if (callback) callback('No Query', []); return; } lang = lang || 'en'; - d3_json(endpoint.replace('en', lang) + + var url = endpoint.replace('en', lang) + utilQsString({ action: 'query', list: 'search', @@ -27,26 +27,34 @@ export default { format: 'json', origin: '*', srsearch: query - }), function(err, data) { - if (err || !data || !data.query || !data.query.search || data.error) { - callback('', []); - } else { - var results = data.query.search.map(function(d) { return d.title; }); - callback(query, results); + }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } else if (!result || !result.query || !result.query.search) { + throw new Error('No Results'); } - } - ); + if (callback) { + var titles = result.query.search.map(function(d) { return d.title; }); + callback(null, titles); + } + }) + .catch(function(err) { + if (callback) callback(err, []); + }); }, suggestions: function(lang, query, callback) { if (!query) { - callback('', []); + if (callback) callback('', []); return; } lang = lang || 'en'; - d3_json(endpoint.replace('en', lang) + + var url = endpoint.replace('en', lang) + utilQsString({ action: 'opensearch', namespace: 0, @@ -54,24 +62,30 @@ export default { format: 'json', origin: '*', search: query - }), function(err, data) { - if (err || !data || data.error) { - callback('', []); - } else { - callback(data[0], data[1] || []); + }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } else if (!result || result.length < 2) { + throw new Error('No Results'); } - } - ); + if (callback) callback(null, result[1] || []); + }) + .catch(function(err) { + if (callback) callback(err, []); + }); }, translations: function(lang, title, callback) { if (!title) { - callback({}); + if (callback) callback({}); return; } - d3_json(endpoint.replace('en', lang) + + var url = endpoint.replace('en', lang) + utilQsString({ action: 'query', prop: 'langlinks', @@ -79,21 +93,27 @@ export default { origin: '*', lllimit: 500, titles: title - }), function(err, data) { - if (err || !data || !data.query || !data.query.pages || data.error) { - callback({}); - } else { - var list = data.query.pages[Object.keys(data.query.pages)[0]], - translations = {}; + }); + + d3_json(url) + .then(function(result) { + if (result && result.error) { + throw new Error(result.error); + } else if (!result || !result.query || !result.query.pages) { + throw new Error('No Results'); + } + if (callback) { + var list = result.query.pages[Object.keys(result.query.pages)[0]]; + var translations = {}; if (list && list.langlinks) { - list.langlinks.forEach(function(d) { - translations[d.lang] = d['*']; - }); + list.langlinks.forEach(function(d) { translations[d.lang] = d['*']; }); } callback(translations); } - } - ); + }) + .catch(function() { + if (callback) callback({}); + }); } }; diff --git a/modules/svg/data.js b/modules/svg/data.js index 64a0aacc4..5738d1f94 100644 --- a/modules/svg/data.js +++ b/modules/svg/data.js @@ -1,16 +1,8 @@ import _throttle from 'lodash-es/throttle'; -import { - geoBounds as d3_geoBounds, - geoPath as d3_geoPath -} from 'd3-geo'; - -import { text as d3_text } from 'd3-request'; - -import { - event as d3_event, - select as d3_select -} from 'd3-selection'; +import { geoBounds as d3_geoBounds, geoPath as d3_geoPath } from 'd3-geo'; +import { text as d3_text } from 'd3-fetch'; +import { event as d3_event, select as d3_select } from 'd3-selection'; import stringify from 'fast-json-stable-stringify'; import toGeoJSON from '@mapbox/togeojson'; @@ -480,10 +472,14 @@ export function svgData(projection, context, dispatch) { var extension = getExtension(testUrl) || defaultExtension; if (extension) { _template = null; - d3_text(url, function(err, data) { - if (err) return; - drawData.setFile(extension, data); - }); + d3_text(url) + .then(function(data) { + drawData.setFile(extension, data); + }) + .catch(function() { + /* ignore */ + }); + } else { drawData.template(url); } diff --git a/modules/svg/defs.js b/modules/svg/defs.js index 7e8fb264b..31c98967a 100644 --- a/modules/svg/defs.js +++ b/modules/svg/defs.js @@ -1,4 +1,4 @@ -import { request as d3_request } from 'd3-request'; +import { svg as d3_svg } from 'd3-fetch'; import { select as d3_select } from 'd3-selection'; import { utilArrayUniq } from '../util'; @@ -191,11 +191,9 @@ export function svgDefs(context) { .each(function(d) { var url = context.imagePath(d + '.svg'); var node = d3_select(this).node(); - d3_request(url) - .mimeType('image/svg+xml') - .response(function(xhr) { return xhr.responseXML; }) - .get(function(err, svg) { - if (err) return; + + d3_svg(url) + .then(function(svg) { node.appendChild( d3_select(svg.documentElement).attr('id', d).node() ); @@ -203,6 +201,9 @@ export function svgDefs(context) { d3_select(node).selectAll('path') .attr('fill', 'currentColor'); } + }) + .catch(function() { + /* ignore */ }); }); };