
本文深入探讨了javascript promise在链式调用中常见的陷阱,特别是当promise的`.then()`方法未被触发时的问题。通过分析错误的promise构造方式(未调用`resolve`或`reject`)以及不当的promise包装,文章提供了使用`.then()`链式调用和`async/await`语法进行正确重构的示例,旨在帮助开发者构建健壮、高效的异步代码。
在JavaScript异步编程中,Promise是处理异步操作结果的关键工具。然而,不正确的Promise使用方式,尤其是在链式调用和创建Promise实例时,常常会导致.then()回调不被执行,使程序逻辑中断。本文将详细解析这些常见问题,并提供专业的解决方案。
Promise代表了一个异步操作的最终完成(或失败)及其结果值。一个Promise有三种状态:
当一个Promise从pending状态变为fulfilled或rejected时,它会触发相应的.then()或.catch()回调。
许多开发者在使用new Promise()构造函数时,会忽略其核心作用:包装非Promise的异步操作,并通过手动调用resolve或reject来改变Promise的状态。如果构造函数内部的执行器函数(executor function)没有调用resolve或reject,那么这个Promise将永远停留在pending状态,其后续的.then()回调自然也永远不会被执行。
立即学习“Java免费学习笔记(深入)”;
错误示例:
new Promise(function () {
// 这里没有调用 resolve 或 reject
updateToDefaultLayerSetting();
}).then(function() {
// 这段代码永远不会执行
console.log("Promise resolved!");
});在这个例子中,updateToDefaultLayerSetting()函数可能执行了异步操作,但它并没有直接与这个new Promise实例的resolve或reject关联。因此,这个Promise将永远不会改变状态。
正确创建Promise的示例:
当需要将一个基于回调的异步操作转换为Promise时,应确保调用resolve或reject:
new Promise(function (resolve, reject) {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('操作成功!'); // 成功时调用 resolve
} else {
reject('操作失败'); // 失败时调用 reject
}
}, 1000);
}).then(result => {
console.log(result);
}).catch(error => {
console.error(error);
});现代JavaScript中,许多异步API(如fetch、async函数)本身就返回Promise。此时,不应该再使用new Promise()去“包装”这些已经返回Promise的操作,这不仅冗余,还可能导致逻辑错误,如同上述未调用resolve/reject的问题。正确的做法是直接对这些Promise进行链式调用(.then())或使用await。
考虑以下原始代码片段:
function loadBasemap(layers) {
if (LayerSettings && LayerSettings.hasOwnProperty('basemap') && LayerSettings.basemap.hasOwnProperty('baseMapLayers')) {
new Promise(function () { // 陷阱:未调用 resolve/reject
updateToDefaultLayerSetting();
}).then(function () {
map = new Map({
basemap: Basemap.fromJSON(LayerSettings.basemap),
layers: layers
});
});
return new Promise((resolve, reject) => resolve(map)); // 陷阱:过早返回一个可能未定义的map
}
// ... else 分支
}这里存在两个主要问题:
updateToDefaultLayerSetting是一个async函数,它本身就返回一个Promise。因此,我们应该直接利用这个Promise。
通过正确地链式调用Promise,我们可以确保异步操作按预期顺序执行,并将最终结果传递下去。
async function updateToDefaultLayerSetting() {
// ... 保持原样,它是一个 async 函数,会返回一个 Promise
console.log("Calling default");
const result = await actionDefaultBasemap(); // 确保 actionDefaultBasemap 也返回一个 Promise
console.log(result);
}
// 重构 loadBasemap
function loadBasemap(layers) {
if (LayerSettings && LayerSettings.hasOwnProperty("basemap") && LayerSettings.basemap.hasOwnProperty("baseMapLayers")) {
// 直接调用 updateToDefaultLayerSetting(),它返回一个 Promise
return updateToDefaultLayerSetting().then(function () {
// 当 updateToDefaultLayerSetting 完成后,执行这里的代码
const mapInstance = new Map({
basemap: Basemap.fromJSON(LayerSettings.basemap),
layers: layers,
});
return mapInstance; // 返回 mapInstance,作为此 Promise 链的最终结果
});
} else {
// 处理 else 分支,例如返回一个已解析的 Promise 或抛出错误
return Promise.reject(new Error("LayerSettings.basemap.baseMapLayers not found"));
}
}actionDefaultBasemap的重构建议:
actionDefaultBasemap也存在类似问题,它创建了一个不必要的new Promise且未调用resolve/reject。由于portalA.load()返回一个Promise,我们应该直接利用它。
function actionDefaultBasemap() {
let portalA = new Portal(portalConfig);
// 直接对 portalA.load() 返回的 Promise 进行链式调用
return portalA.load().then(function () {
defaultBasemap = portalA.useVectorBasemaps ? portalA.defaultVectorBasemap : portalA.defaultBasemap;
// 返回一个 Promise,确保后续的 .then 能够等待到 defaultBasemap 赋值完成
return new Promise(resolve => { // 这里可以手动创建一个 Promise 来确保后续操作等待
if (LayerSettings.basemap.baseMapLayers[0].hasOwnProperty('url') && typeof defaultBasemap.resourceInfo !== "undefined") {
LayerSettings.basemap.baseMapLayers[0].id = defaultBasemap.resourceInfo.data.baseMapLayers[0].id;
LayerSettings.basemap.baseMapLayers[0].title = defaultBasemap.resourceInfo.data.baseMapLayers[0].title;
LayerSettings.basemap.baseMapLayers[0].url = defaultBasemap.resourceInfo.data.baseMapLayers[0].url;
}
resolve(defaultBasemap); // 确保在所有操作完成后解析
});
});
}更优的actionDefaultBasemap重构(避免嵌套Promise):
如果LayerSettings的修改是同步的,可以直接在.then回调中完成并返回defaultBasemap。
function actionDefaultBasemap() {
let portalA = new Portal(portalConfig);
return portalA.load().then(() => {
defaultBasemap = portalA.useVectorBasemaps ? portalA.defaultVectorBasemap : portalA.defaultBasemap;
if (LayerSettings.basemap.baseMapLayers[0].hasOwnProperty('url') && typeof defaultBasemap.resourceInfo !== "undefined") {
LayerSettings.basemap.baseMapLayers[0].id = defaultBasemap.resourceInfo.data.baseMapLayers[0].id;
LayerSettings.basemap.baseMapLayers[0].title = defaultBasemap.resourceInfo.data.baseMapLayers[0].title;
LayerSettings.basemap.baseMapLayers[0].url = defaultBasemap.resourceInfo.data.baseMapLayers[0].url;
}
return defaultBasemap; // 直接返回 defaultBasemap
});
}async/await是ES2017引入的语法糖,它建立在Promise之上,使异步代码看起来和行为更像同步代码,大大提高了可读性。
// 重构 loadBasemap
async function loadBasemap(layers) {
if (LayerSettings && LayerSettings.hasOwnProperty("basemap") && LayerSettings.basemap.hasOwnProperty("baseMapLayers")) {
await updateToDefaultLayerSetting(); // 等待 updateToDefaultLayerSetting 完成
const mapInstance = new Map({
basemap: Basemap.fromJSON(LayerSettings.basemap),
layers: layers,
});
return mapInstance; // 返回 mapInstance
} else {
// 处理 else 分支
throw new Error("LayerSettings.basemap.baseMapLayers not found");
}
}
// 重构 actionDefaultBasemap
async function actionDefaultBasemap() {
let portalA = new Portal(portalConfig);
await portalA.load(); // 等待 portalA.load() 完成
defaultBasemap = portalA.useVectorBasemaps ? portalA.defaultVectorBasemap : portalA.defaultBasemap;
if (LayerSettings.basemap.baseMapLayers[0].hasOwnProperty('url') && typeof defaultBasemap.resourceInfo !== "undefined") {
LayerSettings.basemap.baseMapLayers[0].id = defaultBasemap.resourceInfo.data.baseMapLayers[0].id;
LayerSettings.basemap.baseMapLayers[0].title = defaultBasemap.resourceInfo.data.baseMapLayers[0].title;
LayerSettings.basemap.baseMapLayers[0].url = defaultBasemap.resourceInfo.data.baseMapLayers[0].url;
}
return defaultBasemap; // 返回 defaultBasemap
}使用async/await后,doStart函数可以这样调用:
async function doStart(){
try {
var loadedMap = await loadBasemap([layer0]); // 等待 loadBasemap 完成并获取结果
view = loadView(MAP_CANVAS_ID, loadedMap); // 直接使用返回的 map 对象
// ... 其他逻辑
} catch (error) {
console.error("加载地图失败:", error);
}
}通过遵循这些原则,开发者可以有效地避免Promise不进入.then()的问题,构建出更健壮、可维护的JavaScript异步应用。
以上就是JavaScript Promise 链式调用与常见陷阱解析的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号