嵌套异步循环内函数调用的异步错误
async errors with function call inside nested async loop
我正在制作坑洼图,现在我正在从服务器加载坑洼数据并将其作为标记放在地图上。由于数据检索和我的应用程序所依赖的 API(道路、地理位置等)是异步的,因此我的代码最终重构为 运行 异步。我重构了将标记添加到地图的代码块:
/* put all potholes on the map
* Parameters:
* • callback : the function to call next
*/
function addPotholeMarkers(callback)
{
var DEBUG = false;
// guarantee that callback is function
if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from addPotholeMarkers().');
// add all the markers for them to the map
async.waterfall([
function(cb) {
async.eachOf(potholeAddresses, function(value, key) {
async.eachOf(value, function (v, k) {
addPotholeMarker(v, false);
})
})
if (cb && typeof cb === 'function') cb(null);
}, function(cb) {
async.eachOf(potholeCoordinates, function(value, key) {
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); }); // This came from
})
})
}], function(err, results) {
console.log('trying to center map');
reCenterMap();
console.log('Map recentered');
if (callback) {
callback(err);
}
});
}
和 addPotholeMarker()
看起来像:
/* code was initially obtained from https://developers.google.com/maps/documentation/roads/inspector */
/* Adds marker to map.
* Parameters :
* • potholeData : a PotholeData (or PotholeDataFromCoords) object
* • snappedToRoad: boolean
* Returns :
* • the marker that was added to the map, or null if arguments invalid
*/
function addPotholeMarker(potholeData, snappedToRoad) {
// make sure potholeState is either falsy or contains iconURL string
if ((!potholeData.potholeState) || ((potholeData.potholeState) && (potholeData.potholeState.iconURL === undefined))) throw new Error('invalid potholeData');
// let's make sure to snap this to road if it isn't already...
var coords = new GPSCoordinates(potholeData.lat, potholeData.lng);
if (!snappedToRoad)
{
var potholeMarker = 'a garbage return value';
getRoadCoordinates(coords).done(function(response) {
var coords = response.snappedPoints[0].location;
potholeData.lat = coords.latitude;
potholeData.lng = coords.longitude;
return (potholeMarker = addPotholeMarker(potholeData, true));
/* potholeMarker = addPotholeMarker(potholeData, true);
return potholeMarker;*/
});
return;
//return potholeMarker;
}
var marker = new google.maps.Marker({
position: coords,
title: coords.toString(),
map: map,
opacity: 0.5,
icon: ((potholeData.potholeState.iconURL !== undefined) ? potholeData.potholeState.iconURL : PURPLE_MARKER)
});
// make marker have effect when mouseout,mouseover
marker.addListener('mouseover', function(mouseEvent) {
marker.setOpacity(1.0);
});
marker.addListener('mouseout', function(mouseEvent) {
marker.setOpacity(0.5);
});
var infoWindow = createInfoWindow(potholeData);
// save infoWindow for later reference
infoWindows[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(infoWindow);
// on click of marker, show infoWindow
marker.addListener('click', function(mouseEvent) {
infoWindow.open(map, marker);
});
// add this to potholeMarkers
potholeMarkers[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(marker);
return marker;
}
This app is hosted on Google Apps Script (you'll need Google account to run this), and uses the client-side async library
这段代码,当 运行 成功时,应该将地图重新居中于所有标记的平均位置。 reCenterMap()
正常工作,所以我在尝试 MVCE 时省略了它。
当我运行代码
在异步循环的任何滴答期间,potholeCoordinates
对象(Object<Array<PotholeData> >
)的成员显示为空。如何解决这个问题?
给朋友看了这个问题,考虑了他的建议,昨晚熬了几个小时自己解决了,我做了如下修改:
- 与这个问题无关,但是
我将索引 statesMap.get(getPotholeStateFor(potholeData.potholeState))
更改为 statesMap.get(getPotholeStateFor(potholeData))
。原来我给了 getPotholeStateFor()
错误的对象,这使得它 return 错误的状态。
- 我把
addPotholeMarker(potholeData, snappedToRoad)
的签名改成了addPotholeMarker(potholeData, snappedToRoad, callback)
; /* 因为显然我忘记了,对于异步函数,回调必须传递给最低级别的函数并在那里调用,带有签名 callback(err, results)
或类似的东西 */
在 addPotholeMarker()
中,我确保使用 callback
,但以模块化方式:
if (callback) return callback(null, potholeMarker);
return potholeMarker;
/* 我将此更改应用到 if (!snappedToRoad)
,但该 if 语句仍然损坏:它将 return 在完成追加到数组的任务之前,并且 return callback(null, potholeMarker)
将导致回调被调用两次。我可能最终不得不重构整个函数,尤其是附加到 potholeMarkers
(顺便说一句,它是全局的)after 这个函数(它将是对此函数的回调) */
addPotholeMarkers()
中的最内层异步循环已更改为:
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); });
})
到
async.eachSeries(value, function(pothole, fn) {
addPotholeMarker(pothole,
true,
//pothole.isSnappedToRoad(),
fn);
})
注意 pothole.isSnappedToRoad()
被注释掉,因为它 return 是假的,并且 addPotholeMarker()
在主调用堆栈中不能正常工作第二个参数为假。此外,它应该 return 为真,但这不是因为这种类型的错误发生在 addPotholeMarkers()
之前的函数中; /* 接下来我要解决这个问题! */
我正在制作坑洼图,现在我正在从服务器加载坑洼数据并将其作为标记放在地图上。由于数据检索和我的应用程序所依赖的 API(道路、地理位置等)是异步的,因此我的代码最终重构为 运行 异步。我重构了将标记添加到地图的代码块:
/* put all potholes on the map
* Parameters:
* • callback : the function to call next
*/
function addPotholeMarkers(callback)
{
var DEBUG = false;
// guarantee that callback is function
if ((callback) && (typeof(callback) !== 'function')) throw new TypeError('callback is something, but not a function. Thrown from addPotholeMarkers().');
// add all the markers for them to the map
async.waterfall([
function(cb) {
async.eachOf(potholeAddresses, function(value, key) {
async.eachOf(value, function (v, k) {
addPotholeMarker(v, false);
})
})
if (cb && typeof cb === 'function') cb(null);
}, function(cb) {
async.eachOf(potholeCoordinates, function(value, key) {
async.eachOf(value, function(v, k) {
async.setImmediate(function() { addPotholeMarker(v); }); // This came from
})
})
}], function(err, results) {
console.log('trying to center map');
reCenterMap();
console.log('Map recentered');
if (callback) {
callback(err);
}
});
}
和 addPotholeMarker()
看起来像:
/* code was initially obtained from https://developers.google.com/maps/documentation/roads/inspector */
/* Adds marker to map.
* Parameters :
* • potholeData : a PotholeData (or PotholeDataFromCoords) object
* • snappedToRoad: boolean
* Returns :
* • the marker that was added to the map, or null if arguments invalid
*/
function addPotholeMarker(potholeData, snappedToRoad) {
// make sure potholeState is either falsy or contains iconURL string
if ((!potholeData.potholeState) || ((potholeData.potholeState) && (potholeData.potholeState.iconURL === undefined))) throw new Error('invalid potholeData');
// let's make sure to snap this to road if it isn't already...
var coords = new GPSCoordinates(potholeData.lat, potholeData.lng);
if (!snappedToRoad)
{
var potholeMarker = 'a garbage return value';
getRoadCoordinates(coords).done(function(response) {
var coords = response.snappedPoints[0].location;
potholeData.lat = coords.latitude;
potholeData.lng = coords.longitude;
return (potholeMarker = addPotholeMarker(potholeData, true));
/* potholeMarker = addPotholeMarker(potholeData, true);
return potholeMarker;*/
});
return;
//return potholeMarker;
}
var marker = new google.maps.Marker({
position: coords,
title: coords.toString(),
map: map,
opacity: 0.5,
icon: ((potholeData.potholeState.iconURL !== undefined) ? potholeData.potholeState.iconURL : PURPLE_MARKER)
});
// make marker have effect when mouseout,mouseover
marker.addListener('mouseover', function(mouseEvent) {
marker.setOpacity(1.0);
});
marker.addListener('mouseout', function(mouseEvent) {
marker.setOpacity(0.5);
});
var infoWindow = createInfoWindow(potholeData);
// save infoWindow for later reference
infoWindows[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(infoWindow);
// on click of marker, show infoWindow
marker.addListener('click', function(mouseEvent) {
infoWindow.open(map, marker);
});
// add this to potholeMarkers
potholeMarkers[statesMap.get(getPotholeStateFor(potholeData.potholeState))].push(marker);
return marker;
}
This app is hosted on Google Apps Script (you'll need Google account to run this), and uses the client-side async library
这段代码,当 运行 成功时,应该将地图重新居中于所有标记的平均位置。 reCenterMap()
正常工作,所以我在尝试 MVCE 时省略了它。
当我运行代码
在异步循环的任何滴答期间,potholeCoordinates
对象(Object<Array<PotholeData> >
)的成员显示为空。如何解决这个问题?
给朋友看了这个问题,考虑了他的建议,昨晚熬了几个小时自己解决了,我做了如下修改:
- 与这个问题无关,但是
我将索引 statesMap.get(getPotholeStateFor(potholeData.potholeState))
更改为 statesMap.get(getPotholeStateFor(potholeData))
。原来我给了 getPotholeStateFor()
错误的对象,这使得它 return 错误的状态。
- 我把
addPotholeMarker(potholeData, snappedToRoad)
的签名改成了addPotholeMarker(potholeData, snappedToRoad, callback)
; /* 因为显然我忘记了,对于异步函数,回调必须传递给最低级别的函数并在那里调用,带有签名callback(err, results)
或类似的东西 */ 在
addPotholeMarker()
中,我确保使用callback
,但以模块化方式:if (callback) return callback(null, potholeMarker); return potholeMarker;
/* 我将此更改应用到if (!snappedToRoad)
,但该 if 语句仍然损坏:它将 return 在完成追加到数组的任务之前,并且return callback(null, potholeMarker)
将导致回调被调用两次。我可能最终不得不重构整个函数,尤其是附加到potholeMarkers
(顺便说一句,它是全局的)after 这个函数(它将是对此函数的回调) */addPotholeMarkers()
中的最内层异步循环已更改为:async.eachOf(value, function(v, k) { async.setImmediate(function() { addPotholeMarker(v); }); })
到
async.eachSeries(value, function(pothole, fn) {
addPotholeMarker(pothole,
true,
//pothole.isSnappedToRoad(),
fn);
})
注意 pothole.isSnappedToRoad()
被注释掉,因为它 return 是假的,并且 addPotholeMarker()
在主调用堆栈中不能正常工作第二个参数为假。此外,它应该 return 为真,但这不是因为这种类型的错误发生在 addPotholeMarkers()
之前的函数中; /* 接下来我要解决这个问题! */