Chrome中关闭MetaMask登录弹窗未抛出4001错误,Edge正常
问题分析
这是MetaMask在Chrome浏览器下的状态同步异常问题:
- 用户关闭登录弹窗后,MetaMask未正确触发
eth_requestAccounts的Promise拒绝回调(不抛出4001错误),导致前端代码的catch块无法执行,按钮一直处于禁用状态。 - MetaMask后台保留了未完成的请求状态,刷新页面无法清除(扩展状态独立于页面),必须重启浏览器才能重置。
- Edge浏览器的状态同步逻辑与Chrome不同,因此不会出现残留通知问题。
解决方案
针对代码核心问题(未处理Promise pending状态+重复请求),可通过以下方式修复:
1. 添加请求状态锁+超时处理
新增请求状态跟踪避免重复调用,同时给请求设置超时强制重置状态:
import { Card, CardContent, Button, Grid } from '@material-ui/core' import React, { useEffect, useState } from "react"; import MetaMaskOnboarding from '@metamask/onboarding'; function MetaMaskConnectButton(props) { const forwarderOrigin = 'http://localhost:3000'; const onboarding = new MetaMaskOnboarding({ forwarderOrigin }); const { ethereum } = window; const [buttonText, setButtonText] = useState('Connect MetaMask') const [isButtonDisabled, setIsButtonDisabled] = useState(false) const [mmAccounts, setMmAccounts] = useState('') // 新增:跟踪请求是否正在进行 const [isRequestPending, setIsRequestPending] = useState(false) useEffect(() => { if (!isMetaMaskInstalled()) { setButtonText('Click here to install MetaMask!') } }, []) useEffect(() => { if (mmAccounts == null || mmAccounts.length === 0) setIsButtonDisabled(false) else setIsButtonDisabled(true) }, [mmAccounts]) async function metaMaskClientCheck() { if (isRequestPending) return; // 请求进行中直接返回,避免重复触发 if (!isMetaMaskInstalled()) { await metamaskInstall() setIsButtonDisabled(true) } else { await metamaskConnect() } } const isMetaMaskInstalled = () => { return Boolean(ethereum && ethereum.isMetaMask); } const metamaskInstall = async () => { setButtonText('Onboarding in progress...') setIsButtonDisabled(true) onboarding.startOnboarding(); } const metamaskConnect = async () => { setIsRequestPending(true) setIsButtonDisabled(true) setButtonText('Connecting to MetaMask...') try { // 15秒超时处理,避免Promise永久pending const accounts = await Promise.race([ ethereum.request({ method: 'eth_requestAccounts' }), new Promise((_, reject) => setTimeout(() => reject(new Error('Request timed out')), 15000)) ]) console.log('Ethereum Request over') let account = accounts[0] setMmAccounts(account) setButtonText('MetaMask Connected') } catch (error) { console.error(error); // 统一处理用户拒绝或超时情况 setButtonText(error.code === 4001 ? 'Connect MetaMask' : 'Connection failed') setIsButtonDisabled(false) } finally { // 无论成功失败,重置请求状态 setIsRequestPending(false) } }; return ( <Grid container xs={12} sm={12}> <Grid container xs={6} sm={6} justify='flex-start'> <Button disabled={isButtonDisabled} variant='contained' color='secondary' className='button-blue originalText' onClick={() => metaMaskClientCheck()} classes={{ root: 'ng-button-filled-secondary' }} > {buttonText} </Button> </Grid> </Grid> )} export default MetaMaskConnectButton
2. 监听MetaMask状态事件(可选增强)
添加账户变更和断开事件监听,确保前端状态与钱包同步:
useEffect(() => { if (!ethereum) return; // 监听账户变更 const handleAccountsChanged = (accounts) => { if (accounts.length > 0) { setMmAccounts(accounts[0]) setButtonText('MetaMask Connected') } else { setMmAccounts('') setButtonText('Connect MetaMask') } } // 监听断开连接 const handleDisconnect = () => { setMmAccounts('') setButtonText('Connect MetaMask') setIsButtonDisabled(false) } ethereum.on('accountsChanged', handleAccountsChanged) ethereum.on('disconnect', handleDisconnect) return () => { ethereum.removeListener('accountsChanged', handleAccountsChanged) ethereum.removeListener('disconnect', handleDisconnect) } }, [ethereum])
关键说明
Promise.race处理用户长时间不操作或关闭弹窗的情况,避免Promise永久pending。isRequestPending标志位防止重复触发请求,解决"Already processing"错误。- 事件监听确保前端状态与MetaMask钱包状态同步,减少不同步问题。
内容的提问来源于stack exchange,提问作者Cheryl
相关产品推荐
相关产品推荐

