Simulator: E/jswrapper (348): ERROR: Uncaught TypeError: Cannot read property 'catch' of undefined, location: src/cocos2d-jsb.js:0:0 STACK: [0]onLoad@dst/assets/_script/UI_Entry.js:554 [1]eval@(no filename):3 [2]anonymous@src/cocos2d-jsb.js:28200 [3]invoke@src/cocos2d-jsb.js:28150 [4]activateNode@src/cocos2d-jsb.js:39820 [5]_onHierarchyChanged@src/cocos2d-jsb.js:55470 [6]_onHierarchyChanged@src/cocos2d-jsb.js:19288 [7]setParent@src/cocos2d-jsb.js:55210 [8]addChild@src/cocos2d-jsb.js:20081 [9]anonymous@dst/assets/_script/UIManager.js:165 [10]step@src/cocos2d-jsb.js:95219 [11]anonymous@src/cocos2d-jsb.js:95163 [12]fulfilled@src/cocos2d-jsb.js:95126 at HTMLElement.print-simulator-log (C:\ProgramData\cocos\editors\Creator\2.4.13\resources\app.asar\editor\builtin\scene\panel\messages\scene.ccc:1:1451) at Object._dispatch (C:\ProgramData\cocos\editors\Creator\2.4.13\resources\app.asar\editor-framework\lib\renderer\panel.ccc:1:1750) at EventEmitter.<anonymous> (C:\ProgramData\cocos\editors\Creator\2.4.13\resources\app.asar\editor-framework\lib\renderer\ipc.ccc:1:2816) at EventEmitter.emit (events.js:315:20) at Object.onMessage (electron/js2c/renderer_init.js:93:1117)
时间: 2025-07-20 08:26:39 浏览: 6
<think>我们遇到了一个运行时错误:`Uncaught TypeError: Cannot read property 'catch' of undefined`,发生在`UI_Entry.js`的`onLoad`方法中的第554行。
错误分析:
1. 错误信息表明我们尝试访问一个`undefined`值的`catch`属性(或方法)。在JavaScript中,这通常发生在Promise处理中,当我们对一个非Promise对象(特别是`undefined`)使用`.catch()`时。
2. 错误发生在`UI_Entry.js`的`onLoad`方法中,具体位置是第554行。
3. 调用栈显示,这个错误发生在节点激活过程中,当组件的`onLoad`方法被调用时。
解决方案步骤:
1. **定位代码位置**:
我们需要在`UI_Entry.js`中找到`onLoad`方法的第554行(或附近)的代码。根据之前的代码,我们使用`EngineReady.safeAccess`方法,它返回一个Promise(如果引擎未准备好)或者直接执行回调(如果引擎已准备好)。
2. **问题代码分析**:
在之前的代码中,`onLoad`方法中有一段:
```javascript
EngineReady.safeAccess(() => {
// ... 代码
}).catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
```
这里我们对`EngineReady.safeAccess`的返回值调用了`.catch`。但是,如果`EngineReady.safeAccess`在引擎已经准备好的情况下,它可能返回`undefined`(因为直接执行回调,没有返回Promise),或者返回一个Promise(当引擎未准备好时)。
3. **修复方法**:
我们需要确保`EngineReady.safeAccess`始终返回一个Promise,这样我们就可以统一使用`.catch`处理错误。
修改`EngineReady.safeAccess`方法,使其在同步执行的情况下也返回一个已解决的Promise,这样我们就可以链式调用`.catch`。
修改`EngineReady`对象中的`safeAccess`方法:
```javascript
safeAccess(callback) {
if (this.isReady) {
try {
const result = callback();
// 如果返回的是Promise,则返回这个Promise,否则返回一个已解决的Promise
if (result && typeof result.then === 'function') {
return result.catch(e => {
console.error('[EngineSafe] Sync callback returned a rejected promise:', e);
});
} else {
return Promise.resolve(result);
}
} catch (e) {
console.error('[EngineSafe] Sync error:', e);
return Promise.reject(e);
}
} else {
return this.promise.then(callback).catch(e => {
console.error('[EngineSafe] Async error:', e);
});
}
}
```
但是,上面的修改可能会使代码复杂化。另一种更简单的方法是:让`safeAccess`始终返回一个Promise。这样,我们就可以在调用后统一使用`.catch`。
我们修改`safeAccess`如下:
```javascript
safeAccess(callback) {
if (this.isReady) {
return new Promise((resolve) => {
try {
const result = callback();
// 如果回调返回一个Promise,则等待它
if (result && typeof result.then === 'function') {
result.then(resolve).catch(e => {
console.error('[EngineSafe] Sync callback promise rejected:', e);
resolve(); // 或者 reject(e) 根据需求
});
} else {
resolve(result);
}
} catch (e) {
console.error('[EngineSafe] Sync callback error:', e);
resolve(); // 或者 reject(e) 根据需求
}
});
} else {
return this.promise.then(callback).catch(e => {
console.error('[EngineSafe] Async error:', e);
});
}
}
```
但是,考虑到我们并不需要回调的返回值,我们可以简化处理:无论同步还是异步,都返回一个Promise,并且在同步错误时也通过Promise捕获。
修改后的`EngineReady`对象:
```javascript
const EngineReady = (function() {
// ... 其他代码不变
return {
// ... 其他属性
safeAccess(callback) {
if (this.isReady) {
return new Promise((resolve, reject) => {
try {
const result = callback();
resolve(result);
} catch (e) {
console.error('[EngineSafe] Sync error:', e);
reject(e);
}
});
} else {
return this.promise.then(callback).catch(e => {
console.error('[EngineSafe] Async error:', e);
});
}
}
};
})();
```
然后,在`UI_Entry`的`onLoad`方法中,我们使用:
```javascript
onLoad() {
EngineReady.safeAccess(() => {
try {
cc.director.getCollisionManager().enabled = true;
this._show();
} catch (e) {
console.error('[UI] onLoad error:', e);
this.startLoadGame();
}
}).catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
}
```
注意:这里我们在`safeAccess`的回调中已经捕获了同步错误,所以实际上外层`.catch`可能不会捕获到同步错误(因为同步错误在回调内部被捕获并处理,然后我们通过`reject`将错误传递出来,但我们在回调中已经处理了错误并调用了`this.startLoadGame()`)。因此,我们可能需要调整错误处理逻辑。
另一种更简洁的做法是:不在回调内部捕获错误,而是统一在Promise的`.catch`中处理。
修改`onLoad`方法:
```javascript
onLoad() {
EngineReady.safeAccess(() => {
cc.director.getCollisionManager().enabled = true;
this._show();
}).catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
}
```
同时,修改`safeAccess`,在同步情况下如果回调抛出错误,则返回一个rejected的Promise:
```javascript
safeAccess(callback) {
if (this.isReady) {
return new Promise((resolve, reject) => {
try {
const result = callback();
// 如果回调返回Promise,则等待它
if (result && typeof result.then === 'function') {
result.then(resolve).catch(reject);
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
});
} else {
return this.promise.then(callback).catch(e => {
// 这里已经打印过错误,我们重新抛出,以便外部catch可以捕获
throw e;
});
}
}
```
这样,无论同步还是异步,错误都会被传递到外部的`.catch`中。
4. **修复后的代码**:
下面是修改后的`EngineReady`对象和`UI_Entry`的`onLoad`方法:
```javascript
const EngineReady = (function() {
// ... 内部状态和init方法保持不变
return {
// ... isReady 和 promise 的getter
init() { /* 保持不变 */ },
safeAccess(callback) {
// 如果引擎已经准备好,同步执行回调,但返回一个Promise
if (this.isReady) {
return new Promise((resolve, reject) => {
try {
const result = callback();
// 处理回调返回Promise的情况
if (result && typeof result.then === 'function') {
result.then(resolve).catch(reject);
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
});
} else {
// 引擎未准备好,返回一个Promise,该Promise在引擎准备好后执行回调
return this.promise.then(callback);
}
}
};
})();
```
注意:去掉了内部`catch`,这样错误会传递到调用链。
在`UI_Entry`的`onLoad`中:
```javascript
onLoad() {
EngineReady.safeAccess(() => {
cc.director.getCollisionManager().enabled = true;
this._show();
})
.catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
}
```
这样修改后,无论`safeAccess`是同步执行还是异步执行,返回的都是Promise,因此可以调用`.catch`方法。
5. **错误处理**:
在`.catch`中,我们调用`this.startLoadGame()`来继续游戏加载,即使引擎访问出错。
6. **测试**:
修改后,我们需要测试两种情况:
- 引擎已经准备好的情况(同步)
- 引擎未准备好的情况(异步)
确保在这两种情况下,错误都能被捕获并处理。
### 总结
我们通过修改`EngineReady.safeAccess`方法,使其始终返回一个Promise,从而可以安全地使用`.catch`进行错误处理。这样修复了`Cannot read property 'catch' of undefined`的错误。
### 修改后的代码
以下是修改后的`EngineReady`完整代码(包括之前的逻辑):
```javascript
// ====== 引擎初始化保护系统 ======
const EngineReady = (function() {
let _promise = null;
let _resolve = null;
let _isReady = false;
let _checkInterval = null;
const safeCheckEngine = () => {
// 引擎已初始化
if (cc && cc.game && cc.game._isEngineInited) {
return true;
}
// 尝试监听初始化事件
if (cc && cc.game && cc.game.once) {
const eventName = cc.game.EVENT_ENGINE_INITED || 'engine-inited';
cc.game.once(eventName, () => {
_resolve && _resolve();
_isReady = true;
_checkInterval && clearInterval(_checkInterval);
});
return true;
}
return false;
};
return {
get isReady() { return _isReady; },
get promise() { return _promise; },
init() {
if (!_promise) {
_promise = new Promise(resolve => {
_resolve = () => {
_isReady = true;
resolve();
_checkInterval && clearInterval(_checkInterval);
};
// 首次检查
if (safeCheckEngine()) return;
// 轮询检查
_checkInterval = setInterval(() => {
safeCheckEngine() && _resolve();
}, 100);
});
}
return _promise;
},
safeAccess(callback) {
if (this.isReady) {
return new Promise((resolve, reject) => {
try {
const result = callback();
// 如果回调返回一个Promise,则等待它
if (result && typeof result.then === 'function') {
result.then(resolve).catch(reject);
} else {
resolve(result);
}
} catch (e) {
reject(e);
}
});
} else {
return this.promise.then(callback);
}
}
};
})();
EngineReady.init();
```
在`UI_Entry`的`onLoad`方法中,我们保持不变:
```javascript
onLoad() {
EngineReady.safeAccess(() => {
cc.director.getCollisionManager().enabled = true;
this._show();
}).catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
}
```
这样修改后,错误应该被正确处理,不会再出现`Cannot read property 'catch' of undefined`。
§§相关问题§§:
1. 在JavaScript中,Promise的错误处理有哪些最佳实践?
2. 如何确保一个函数无论在同步还是异步情况下都能返回Promise?
3. Cocos Creator中,组件生命周期的执行顺序是怎样的?
4. 在Cocos Creator中,如何处理未捕获的异常以避免游戏崩溃?</think>## 解决 "Cannot read property 'catch' of undefined" 错误
这个错误发生在 `UI_Entry.js` 文件的 `onLoad` 方法中,具体在第 554 行。错误表明你尝试在 `undefined` 值上调用 `.catch()` 方法,通常发生在 Promise 链中。
### 错误原因分析
从调用栈可以看出:
```plaintext
[0]onLoad@dst/assets/_script/UI_Entry.js:554
```
错误发生在 `UI_Entry.js` 的 `onLoad` 方法中,具体位置是第 554 行。根据之前的代码,这很可能与 `EngineReady.safeAccess()` 方法的使用有关。
### 修复方案
#### 1. 修改 `EngineReady.safeAccess` 方法
```javascript
// 修改 EngineReady 对象
const EngineReady = (function() {
// ... 其他代码不变 ...
return {
// ... 其他属性和方法 ...
safeAccess(callback) {
if (this.isReady) {
try {
return callback();
} catch (e) {
console.error('[EngineSafe] Error:', e);
return Promise.reject(e); // 确保返回 Promise
}
} else {
return this.promise.then(callback).catch(e => {
console.error('[EngineSafe] Async error:', e);
return Promise.reject(e); // 确保返回 Promise
});
}
}
};
})();
```
#### 2. 修改 `UI_Entry` 类的 `onLoad` 方法
```javascript
@ccclass
export default class UI_Entry extends cc.Component {
// ... 其他代码 ...
onLoad() {
// 确保 safeAccess 返回 Promise
const accessPromise = EngineReady.safeAccess(() => {
try {
cc.director.getCollisionManager().enabled = true;
this._show();
} catch (e) {
console.error('[UI] onLoad error:', e);
this.startLoadGame();
return Promise.reject(e); // 确保返回 Promise
}
});
// 安全地处理 Promise
if (accessPromise && typeof accessPromise.catch === 'function') {
accessPromise.catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
} else {
// 如果 safeAccess 没有返回 Promise,直接处理
console.warn('[UI] EngineReady.safeAccess did not return a Promise');
this.startLoadGame();
}
}
// ... 其他代码 ...
}
```
### 关键修复点解释
1. **确保 safeAccess 始终返回 Promise**:
- 在同步执行路径(引擎已准备好)时,我们捕获可能的异常并返回 `Promise.reject()`
- 在异步执行路径(引擎未准备好)时,我们确保返回 Promise 链
2. **安全处理 Promise**:
- 在 `onLoad` 方法中,我们检查 `safeAccess` 的返回值是否是一个 Promise
- 如果是 Promise,则安全地添加 `.catch()` 处理器
- 如果不是 Promise,则直接处理错误情况
3. **错误处理增强**:
- 添加了详细的日志记录,帮助定位问题
- 确保所有错误路径都能触发 `startLoadGame()` 方法
### 完整修复后的 `EngineReady` 和 `UI_Entry` 代码
```javascript
// ====== 引擎初始化保护系统 ======
const EngineReady = (function() {
let _promise = null;
let _resolve = null;
let _isReady = false;
let _checkInterval = null;
const safeCheckEngine = () => {
if (cc && cc.game && cc.game._isEngineInited) return true;
if (cc && cc.game && cc.game.once) {
const eventName = cc.game.EVENT_ENGINE_INITED || 'engine-inited';
cc.game.once(eventName, () => {
_resolve && _resolve();
_isReady = true;
_checkInterval && clearInterval(_checkInterval);
});
return true;
}
return false;
};
return {
get isReady() { return _isReady; },
get promise() { return _promise; },
init() {
if (!_promise) {
_promise = new Promise(resolve => {
_resolve = () => {
_isReady = true;
resolve();
_checkInterval && clearInterval(_checkInterval);
};
if (safeCheckEngine()) return;
_checkInterval = setInterval(() => {
safeCheckEngine() && _resolve();
}, 100);
});
}
return _promise;
},
safeAccess(callback) {
if (this.isReady) {
try {
const result = callback();
// 如果回调返回 Promise,则返回它
if (result && typeof result.then === 'function') {
return result;
}
// 否则返回已解决的 Promise
return Promise.resolve(result);
} catch (e) {
console.error('[EngineSafe] Sync error:', e);
return Promise.reject(e);
}
} else {
return this.promise.then(callback).catch(e => {
console.error('[EngineSafe] Async error:', e);
return Promise.reject(e);
});
}
}
};
})();
EngineReady.init();
```
```javascript
// ====== UI_Entry 组件 ======
const { ccclass, property } = cc._decorator;
@ccclass
export default class UI_Entry extends cc.Component {
@property(cc.ProgressBar)
progress_loading = null;
loginTimeoutHandler = null;
onLoad() {
// 确保安全访问引擎
const accessResult = EngineReady.safeAccess(() => {
try {
cc.director.getCollisionManager().enabled = true;
this._show();
} catch (e) {
console.error('[UI] onLoad error:', e);
this.startLoadGame();
throw e; // 重新抛出以触发 catch
}
});
// 安全处理 Promise
if (accessResult && typeof accessResult.catch === 'function') {
accessResult.catch(e => {
console.error('[UI] Engine access error:', e);
this.startLoadGame();
});
} else {
console.warn('[UI] EngineReady.safeAccess did not return a Promise');
this.startLoadGame();
}
}
// ... 其他方法保持不变 ...
}
```
### 错误预防的最佳实践
1. **始终检查对象是否存在**:
```javascript
// 在调用方法前检查对象和方法是否存在
if (obj && typeof obj.method === 'function') {
obj.method();
}
```
2. **使用可选链操作符**(如果环境支持):
```javascript
// 使用可选链安全访问属性和方法
obj?.method?.();
```
3. **添加类型检查**:
```javascript
function safeCatch(promise) {
if (promise && typeof promise.catch === 'function') {
return promise.catch(e => console.error(e));
}
return promise;
}
```
4. **使用 try-catch 块**:
```javascript
try {
// 可能出错的代码
} catch (e) {
console.error('Error occurred:', e);
}
```
### 测试验证
1. 在模拟器中运行代码
2. 检查控制台是否不再出现 `Cannot read property 'catch' of undefined` 错误
3. 验证 `startLoadGame()` 方法在错误情况下仍能被正确调用
阅读全文
相关推荐


















