函数内执行Ajax Call失效并抛出警告,如何解决?
问题背景
尝试对包含多个Ajax调用的JavaScript文件进行DRY(Don't Repeat Yourself)优化,编写了makeCall函数统一发起Ajax调用,但修改后出现两个问题:
- 调用
retrieveCountry.php的第二个makeCall未执行(GeoJSON要素未添加到地图) - TypeScript警告:
'await' has no effect on the type of this expression.ts(80007)
所有代码位于一个async函数内,detail为该函数的局部变量。
原可正常运行的代码
const makeCall = (url, type, data, done) => { $.ajax({ url: url, type: type, dataType: "json", data: data, }) .done(done) .fail(function(error) { console.log(error); }); }; const country = await $.ajax({ url: "libs/php/opencage.php", type: "GET", dataType: "json", data: { LAT: x.coords.latitude, LNG: x.coords.longitude, }, }) .done(function(result) { if (result.status.name == "ok") { //populate the detail object with responses from opencage needed to communicate with other apis detail.lat = x.coords.latitude; detail.long = x.coords.longitude; detail.continent = result.data.results[0].components.continent; detail.countryName = result.data.results[0].components.country; detail.countryCode = result.data.results[0].components.country_code; let code = result.data.results[0].components.country_code; //set the select to user country console.log(code); $("#countrySelect").val(code.toUpperCase()); } }) .fail(function(error) { // your error code console.log(error); }); //save needed part of detail object for easy access let latitude = detail.lat; let longitude = detail.long; let countryName = noSpace(detail.countryName); let countryCode = detail.countryCode; let continent = detail.continent; //makecall to retrieve geojson feature details for selected country. makeCall( "libs/php/retrieveCountry.php", "GET", { code: countryCode.toUpperCase() }, function(r) { //add the geojson object to the map L.geoJSON(r).addTo(map); } );
修改后出现问题的代码
const country = await makeCall( "libs/php/opencage.php", "GET", { LAT: x.coords.latitude, LNG: x.coords.longitude, }, function(result) { if (result.status.name == "ok") { //populate the detail object with responses from opencage needed to communicate with other apis detail.lat = x.coords.latitude; detail.long = x.coords.longitude; detail.continent = result.data.results[0].components.continent; detail.countryName = result.data.results[0].components.country; detail.countryCode = result.data.results[0].components.country_code; let code = result.data.results[0].components.country_code; //set the select to user country console.log(code); $("#countrySelect").val(code.toUpperCase()); return detail; } } ); //save needed part of detail object for easy access let latitude = detail.lat; let longitude = detail.long; let countryName = noSpace(detail.countryName); let countryCode = detail.countryCode; let continent = detail.continent; //makecall to retrieve geojson feature details for selected country. This part is never implemented as this feature is not added to the map. makeCall( "libs/php/retrieveCountry.php", "GET", { code: countryCode.toUpperCase() }, function(r) { //add the geojson object to the map L.geoJSON(r).addTo(map); } );
问题原因分析
TypeScript警告原因:
makeCall函数没有返回任何值(默认返回undefined),await对undefined没有等待效果,因此TS提示该警告。原代码中await $.ajax(...)有效是因为$.ajax本身返回Promise,但你封装的makeCall没有把这个Promise返回出来。第二个
makeCall未执行的原因:由于await makeCall(...)无效,代码会直接往下执行,此时第一个Ajax请求还未完成,detail对象的属性(比如countryCode)还未被赋值。后续给第二个makeCall传的参数code: countryCode.toUpperCase()会因为countryCode是undefined而抛出错误,导致第二个请求根本无法正常发起,或者后端接收到无效参数后返回错误,而你没注意到fail里的日志。另外,done回调里的return detail没有任何作用,因为这个返回值不会被makeCall传递出来。
解决方案
步骤1:修改makeCall函数,让它返回Promise
调整makeCall,直接返回$.ajax生成的Promise,这样await就能正确等待请求完成:
const makeCall = (url, type, data) => { return $.ajax({ url: url, type: type, dataType: "json", data: data, }).fail(function(error) { console.log(error); }); };
步骤2:调整调用逻辑,用await替代回调
把原来done回调里的逻辑移到await之后,直接处理请求返回的结果:
// 等待第一个请求完成,拿到结果 const result = await makeCall( "libs/php/opencage.php", "GET", { LAT: x.coords.latitude, LNG: x.coords.longitude, } ); if (result.status.name == "ok") { // 填充detail对象 detail.lat = x.coords.latitude; detail.long = x.coords.longitude; detail.continent = result.data.results[0].components.continent; detail.countryName = result.data.results[0].components.country; detail.countryCode = result.data.results[0].components.country_code; let code = result.data.results[0].components.country_code; console.log(code); $("#countrySelect").val(code.toUpperCase()); } // 此时detail属性已被正确赋值 let latitude = detail.lat; let longitude = detail.long; let countryName = noSpace(detail.countryName); let countryCode = detail.countryCode; let continent = detail.continent; // 第二个请求现在能拿到正确的参数,正常执行 await makeCall( "libs/php/retrieveCountry.php", "GET", { code: countryCode.toUpperCase() } ).then(function(r) { L.geoJSON(r).addTo(map); });
额外说明
如果需要保留makeCall的回调参数(兼容原有调用方式),可以让它同时支持Promise和回调:
const makeCall = (url, type, data, done) => { const promise = $.ajax({ url: url, type: type, dataType: "json", data: data, }).fail(function(error) { console.log(error); }); if (done) { promise.done(done); } return promise; };
这样既可以用await调用,也可以继续用回调方式调用,兼顾新旧代码。
内容的提问来源于stack exchange,提问作者Blakjay116

