JavaScript技术问询:类包裹try/catch及链式断言方法实现
问题1:是否需要将JavaScript类的所有代码都包裹在try/catch语句中?
绝对不需要把类里的所有代码都塞进try/catch里,这完全是过度防御的操作,反而会把代码搞得臃肿难维护,甚至掩盖真正的问题。给你拆解下原因:
- 精准捕获才是正道:
try/catch应该用在明确可能抛出异常且你能处理的场景,比如处理用户输入的非法值、异步请求的错误、第三方库调用的异常。如果把所有代码都包起来,像语法错误、逻辑bug这类本该暴露出来方便调试的问题,会被你强行掩盖,反而增加排查难度。 - 让调用者决定错误处理:类的方法有时候需要向外抛出异常,把处理权交给调用方更合理。比如一个处理文件的类,读取失败时抛出异常,调用这个类的业务代码可以根据场景决定是重试、提示用户还是记录日志,而不是在类内部就把错误吞掉。
- 性能损耗不可忽视:虽然现代JS引擎对
try/catch做了优化,但大范围包裹还是会影响代码的执行效率,没必要为了“安全感”牺牲性能。
问题2:如何实现一个类,使其具备类似Mocha.js中
expect(..).to.not.equal()的链式调用方法? 链式调用的核心很简单:每个方法(包括to、not这种标记性的属性)都返回当前类的实例,这样就能一直链式调用下去。下面先给你基础版本,再附上支持异步函数的更新版。
基础同步版本
class Expect { constructor(value) { this.value = value; this.isNot = false; // 标记是否需要取反断言结果 } // 只是链式的中间标记,直接返回实例本身 get to() { return this; } // 切换取反状态,然后返回实例 get not() { this.isNot = !this.isNot; return this; } // 核心断言方法:判断相等 equal(expected) { const isEqual = this.value === expected; // 根据isNot标记决定是否取反结果 const pass = this.isNot ? !isEqual : isEqual; if (!pass) { throw new Error(`Expected ${this.value} ${this.isNot ? 'not ' : ''}to equal ${expected}`); } return this; // 继续支持后续链式调用 } // 扩展:深相等断言 deepEqual(expected) { const isDeepEqual = JSON.stringify(this.value) === JSON.stringify(expected); const pass = this.isNot ? !isDeepEqual : isDeepEqual; if (!pass) { throw new Error(`Expected ${JSON.stringify(this.value)} ${this.isNot ? 'not ' : ''}to deep equal ${JSON.stringify(expected)}`); } return this; } } // 对外暴露工厂函数,调用更简洁 function expect(value) { return new Expect(value); } // 测试示例 try { expect(5).to.equal(5); // 正常通过 expect(5).to.not.equal(6); // 正常通过 expect({a: 1}).to.deepEqual({a: 1}); // 正常通过 expect(5).to.equal(6); // 抛出错误:Expected 5 to equal 6 } catch (e) { console.error(e.message); }
支持异步函数的更新版本
如果要支持异步值(比如Promise),我们需要把断言方法改成异步函数,同时在构造函数里统一处理同步和异步值:
class AsyncExpect { constructor(value) { // 把传入的值统一包装成Promise,同步值也转成resolved的Promise this.promise = typeof value.then === 'function' ? value : Promise.resolve(value); this.isNot = false; this.error = null; // 存储reject的错误信息,方便后续断言 } get to() { return this; } get not() { this.isNot = !this.isNot; return this; } // 异步相等断言 async equal(expected) { const actual = await this.promise; const isEqual = actual === expected; const pass = this.isNot ? !isEqual : isEqual; if (!pass) { throw new Error(`Expected ${actual} ${this.isNot ? 'not ' : ''}to equal ${expected}`); } return this; } // 异步深相等断言 async deepEqual(expected) { const actual = await this.promise; const isDeepEqual = JSON.stringify(actual) === JSON.stringify(expected); const pass = this.isNot ? !isDeepEqual : isDeepEqual; if (!pass) { throw new Error(`Expected ${JSON.stringify(actual)} ${this.isNot ? 'not ' : ''}to deep equal ${JSON.stringify(expected)}`); } return this; } // 新增:断言Promise是否reject async rejects() { try { await this.promise; // 如果Promise resolved,但我们预期它reject,就抛出错误 if (!this.isNot) { throw new Error('Expected promise to reject, but it resolved'); } } catch (err) { // 如果Promise rejected,但我们预期它不reject,抛出错误 if (this.isNot) { throw new Error(`Expected promise not to reject, but it rejected with: ${err.message}`); } this.error = err; // 存储错误,方便后续链式断言错误信息 return this; } return this; } // 扩展:断言reject的错误信息 async withMessage(message) { if (!this.error) { throw new Error('No rejected error to assert'); } const match = this.error.message.includes(message); const pass = this.isNot ? !match : match; if (!pass) { throw new Error(`Expected rejected error ${this.isNot ? 'not ' : ''}to include message: "${message}"`); } return this; } } function expect(value) { return new AsyncExpect(value); } // 异步测试示例 (async () => { try { await expect(Promise.resolve(5)).to.equal(5); await expect(Promise.resolve(5)).to.not.equal(6); await expect(Promise.reject(new Error('test error'))).to.rejects().withMessage('test error'); await expect(Promise.resolve(5)).to.rejects(); // 抛出错误:Expected promise to reject, but it resolved } catch (e) { console.error(e.message); } })();
这个异步版本的核心是:把所有传入的值都包装成Promise,断言方法用async/await处理异步逻辑,同时新增了针对Promise拒绝场景的断言,更贴近测试库的实际使用需求。
内容的提问来源于stack exchange,提问作者jAckOdE
相关产品推荐
相关产品推荐

