React+Firebase应用异步调用顺序问题:成员删除逻辑异常
问题描述
我正在为自己的足球队开发一款React应用,用于记录每位成员的缴费与欠款情况。期望实现的逻辑是:先查询各赛季的数据库记录,若未找到该成员的相关记录,则删除该成员。但当前实际执行顺序为:先删除成员,再执行数据库查询。使用Firebase存储所有数据,相关代码如下:
import database from '../firebase/firebase'; export const startRemoveMember = ( playerUuid, seasonList ) => { return (dispatch, getState) => { let canDelete = true; const uid = getState().auth.uid; // 期望这部分先执行完成 seasonList.forEach( (season) => { database.ref(`subs-tracker/users/${uid}/debts_and_payments/${season.seasonUuid}`) .once('value') .then((records) => { records.forEach((childRecord) => { if(childRecord.val().playerUuid === playerUuid) { canDelete = false; return true; // 找到记录就终止循环 }; }); }); }); // 实际这部分先执行 if(canDelete) { alert('Deleted'); return database.ref(`subs-tracker/users/${uid}/members/${playerUuid}`) .remove() .then((ref) => { dispatch(removeMember(playerUuid)); // 从应用状态中移除成员 }) } else { alert('Cannot Delete. Member has records'); return false; } }; };
问题原因
Firebase的once('value')是异步操作,会返回一个Promise。你用forEach遍历赛季发起查询时,这些查询会在后台异步执行,而主线程会直接跳过这部分,先执行后面的if(canDelete)判断——此时canDelete还没被异步回调修改,默认值为true,导致直接执行删除操作。
解决方案
需要用Promise.all()等待所有赛季的查询请求完成后,再判断是否可以删除成员。修改后的代码如下:
import database from '../firebase/firebase'; export const startRemoveMember = (playerUuid, seasonList) => { return (dispatch, getState) => { const uid = getState().auth.uid; // 把每个赛季的查询转换成Promise数组 const checkRecordPromises = seasonList.map(season => { return database.ref(`subs-tracker/users/${uid}/debts_and_payments/${season.seasonUuid}`) .once('value') .then(records => { // 检查当前赛季是否有该成员的记录 return records.some(childRecord => childRecord.val().playerUuid === playerUuid); }); }); // 等待所有查询完成 return Promise.all(checkRecordPromises) .then(results => { // 只要有一个赛季存在该成员的记录,就不能删除 const hasRecords = results.some(result => result === true); if (!hasRecords) { alert('Deleted'); return database.ref(`subs-tracker/users/${uid}/members/${playerUuid}`) .remove() .then(() => { dispatch(removeMember(playerUuid)); }); } else { alert('Cannot Delete. Member has records'); return false; } }) .catch(error => { console.error('Error checking records:', error); alert('Error verifying records, please try again'); }); }; };
关键修改点
- 用
map替代forEach,将每个赛季的查询转换为Promise并存入数组 - 用
Promise.all()等待所有Promise完成,确保所有查询执行完毕后再进行判断 - 使用
some()方法简化记录存在性判断:只要有一个赛季返回true(存在记录),就标记为不能删除 - 增加错误捕获,处理查询过程中可能出现的异常
内容的提问来源于stack exchange,提问作者Luke Marvelly
相关产品推荐
相关产品推荐

