如何捕获web3.js中_fireError抛出的未处理交易回滚错误?
问题
调用web3.js的mintBatch方法时,交易被EVM回滚,但自定义的错误捕获逻辑未触发——既没打印"mintBatch failed",还出现未处理运行时错误。
调用代码
contract.methods.mintBatch(quantity, data.nonce, data.hash, data.signature, data.nftIds, 1) .send({ from: walletId, value: res * quantity }, (err: any, res: any) => { if (err) { console.error('mintBatch failed, error: ', err); reject(); } else { console.log('mintBatch succeed, res: ', res); resolve({ txHash: res, nftIds: data.nftIds }); } })
未处理错误信息
Unhandled Runtime Error
Error: Transaction has been reverted by the EVM:
{
"blockHash": "0x2d7a0a4e5469e68250eaee0c52eb80559d6f9e2a8873ab4f7b2f47e5e9b973b3",
"blockNumber": 11284056,
"contractAddress": null,
"cumulativeGasUsed": 368714,
"effectiveGasPrice": 2500000009,
"from": "somewhere",
"gasUsed": 76047,
"logsBloom": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"status": false,
"to": "somewhere else",
"transactionHash": "a random transaction hash",
"transactionIndex": 2,
"type": "0x2",
"events": {}
}
错误来源:web3.js内部_fireError函数
var _fireError = function (error, emitter, reject, callback, optionalData) { /*jshint maxcomplexity: 10 */ // add data if given if (!!error && typeof error === 'object' && !(error instanceof Error) && error.data) { if (!!error.data && typeof error.data === 'object' || Array.isArray(error.data)) { error.data = JSON.stringify(error.data, null, 2); } error = error.message + "\n" + error.data; } if (typeof error === 'string') { error = new Error(error); } if (typeof callback === 'function') { callback(error, optionalData); } if (typeof reject === 'function') { // suppress uncatched error if an error listener is present // OR suppress uncatched error if an callback listener is present if (emitter && (typeof emitter.listeners === 'function' && emitter.listeners('error').length) || typeof callback === 'function') { emitter.catch(function () { }); } // reject later, to be able to return emitter setTimeout(function () { reject(error); }, 1); } if (emitter && typeof emitter.emit === 'function') { // emit later, to be able to return emitter setTimeout(function () { emitter.emit('error', error, optionalData); emitter.removeAllListeners(); }, 1); } return emitter; };
解决方案
从_fireError的逻辑来看,错误会同时触发callback、reject和emitter的error事件,但原代码未覆盖所有触发路径,导致未捕获错误。可按以下方式处理:
1. 同时监听send()返回的Promise和error事件
web3.js的send()返回PromiEvent(兼具Promise和事件发射器特性),需同时处理两种错误触发方式:
contract.methods.mintBatch(quantity, data.nonce, data.hash, data.signature, data.nftIds, 1) .send({ from: walletId, value: res * quantity }) .on('error', (err) => { console.error('mintBatch failed (event listener): ', err); reject(err); }) .then((receipt) => { console.log('mintBatch succeed, receipt: ', receipt); resolve({ txHash: receipt.transactionHash, nftIds: data.nftIds }); }) .catch((err) => { console.error('mintBatch failed (promise catch): ', err); reject(err); });
2. 避免重复触发reject
若代码包裹在new Promise()中,需标记错误已处理,防止重复调用reject:
const mintPromise = new Promise((resolve, reject) => { const sendPromiEvent = contract.methods.mintBatch(quantity, data.nonce, data.hash, data.signature, data.nftIds, 1) .send({ from: walletId, value: res * quantity }); let isRejected = false; const handleError = (err) => { if (!isRejected) { console.error('mintBatch failed: ', err); isRejected = true; reject(err); } }; sendPromiEvent.on('error', handleError); sendPromiEvent.catch(handleError); sendPromiEvent.then((receipt) => { console.log('mintBatch succeed, receipt: ', receipt); resolve({ txHash: receipt.transactionHash, nftIds: data.nftIds }); }); });
3. 组件层面捕获异步错误
web3.js用setTimeout异步触发错误,React组件需显式处理Promise错误:
const handleMint = async () => { try { const result = await mintPromise; // 处理成功逻辑 } catch (err) { console.error('Mint failed: ', err); // 向用户展示错误提示 } };
也可使用React的ErrorBoundary组件,捕获组件树中所有未处理的JavaScript错误。
内容的提问来源于stack exchange,提问作者Terry Windwalker

