在另一个单独的异步调用解决之前,如何防止/锁定函数返回?
Posted
技术标签:
【中文标题】在另一个单独的异步调用解决之前,如何防止/锁定函数返回?【英文标题】:how to prevent/lock a function from returning until another separate async call has resolved? 【发布时间】:2022-01-19 18:07:58 【问题描述】:我正在处理路由身份验证,并将身份验证状态存储在上下文中,以便其他 React 组件可以检查用户是否已登录。代码的相关部分是:
const [loggedInUser, setLoggedInUser] = useState(null)
const authed = () => !!loggedInUser
useEffect(() =>
async function fetchData()
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)
fetchData()
, [])
此代码的快速解释是,我需要部分代码中的用户数据(例如 id),因此我需要存储 loggedInUser
对象。但是,对于更简单的任务,例如检查用户是否已登录,我使用函数 authed
来检查 loggedInUser
变量是否包含对象(用户已登录)或为空。
为了检查用户是否已登录,我使用的是 passport.js,我的 '/loggedInUser'
路由如下所示:
app.get('/loggedInUser', async (req, res) =>
if (!req.isAuthenticated())
return null
const id, name = await req.user
.then(res => res.dataValues)
return res.json( id, name )
)
问题是,在useEffect()
和fetch()
能够访问GET 路由并使用对setLoggedInUser(response)
的API 响应之前,使用此上下文并检查authed()
的代码正在运行。所以使用authed()
总是返回false,即使API 响应稍后将loggedInUser
设置为现在authed()
为true 的某个对象值。显然这是一个竞争条件,但我不知道如何解决它。
是否有一个优雅的解决方案,我可以“锁定”authed()
从返回值直到 useEffect() 完全“设置”loggedInUser
的状态?
我设想的一个(糟糕的)解决方案可能是这样的:
const [loggedInUser, setLoggedInUser] = useState(null)
const isFinishedLoading = false // <--
function authed()
while (!isFinishedLoading) // a crude lock
return !!loggedInUser
useEffect(() =>
async function fetchData()
const response = await fetch('/loggedInUser')
.then(res => res.json())
setLoggedInUser(response)
isFinishedLoading = true // <--
fetchData()
, [])
有没有更好的方法来“锁定”函数authed()
直到加载完成?
编辑:
为了澄清我对 cmets 的 authed()
用法,这里是我的 App.js
的精简版
export default function App()
return (
<>
<AuthProvider>
<Router className="Router">
<ProtectedRoute path="/" component=SubmittalTable />
<Login path="/login" />
</Router>
</AuthProvider>
</>
)
function ProtectedRoute( component: Component, ...rest )
const authed = useContext(AuthContext)
if (!authed()) // due to race condition, authed() is always false
return (<Redirect from="" to="login" noThrow />)
return (<Component ...rest />)
【问题讨论】:
不要混用await
和 .then
。他们正在尝试做类似的事情。
“上下文”是指 React.Context 吗?你传递给提供者什么?您是否尝试将 isLoggedIn
提供给 Provider?
这相当广泛,每次页面刷新时您都会发送一个 http 请求以检查用户是否仍然登录。我建议查看一些有关会话管理的文章。
您将难以实现“锁定”。一旦验证了身份验证并加载了数据,您是否只能通过条件语句呈现数据?或者,如果用户未通过身份验证,则将用户重定向到登录页面,并在确认身份验证后重定向回来?您可以将身份验证存储在集中状态,然后在请求从 api 返回 401 时重定向用户。无论哪种方式,您都不能阻止组件的渲染。
@AxisStarstreamer 不,我将其用作 AuthContext,类似于reactjs.org/docs/context.html#when-to-use-context。我将 value=isLoggedIn, authed 传递给提供者,但这并不能解决我的竞争条件问题。 isLoggedIn
将共享相同的竞争条件问题。
【参考方案1】:
我不明白为什么你需要authed()
成为一个函数,并且不要直接使用isLoggedIn
。我也看不到你在哪里 设置 Context 值,但无论如何......
一般建议
通常:在 React 中,尝试思考
“我的应用在任何特定时刻的状态是什么”, 而不是“应该按什么顺序发生什么”。在你的情况下:
“根据状态,现在应该显示哪个页面”, 而不是“一有事情发生就重定向”。用户已授权或未授权在任何给定时刻使用您的应用程序。那是一个状态,你可以把这个状态存储在某个地方。我们称这个状态为isAuthorized
。
存储状态的不同位置
您可以将isAuthorized
存储在上下文 中,只要您知道它在您需要时可用。如果 Context 在您想知道用户是否被授权时不可用(在您的应用中似乎就是这种情况),那么您不能使用用于存储isAuthorized
的上下文(至少不是单独的)。
您可以在每次需要时获取 isAuthorized
。然后 isAuthorized
在 fetch 响应之前不可用。您的应用程序现在的状态是什么?它是notReady
(可能)。您可以将状态 notReady
存储在某处,例如再次在上下文中。 (notReady
最初始终是 true
,因此只有当您明确表示时,您才知道应用程序已准备就绪。)应用程序可能会显示一个 Spinner,只要它是 notReady
,就不会执行任何其他操作。
您可以将isAuthorized
存储在例如浏览器存储(例如sessionStorage
)。它们在页面加载时可用,因此您不必每次都获取状态。浏览器存储应该是同步的,但实际上我会将它们视为异步的,因为我所阅读的有关浏览器存储的内容并没有激发信心。
问题及解决办法
您要做的是将isAuthorized
存储在 (1) 上下文中并且 (2) 每次都获取它,因此您有 2 个需要同步的状态。无论如何,您确实需要在开始时至少获取一次isAuthorized
,否则,应用程序将无法使用。因此,您确实需要同步和状态 (3) notReady
(或 isReady
)。
在 React 中使用 useEffect
完成同步状态(或使用“依赖项”),例如:
useEffect(() =>
setIsFinishedLoading( false ); // state (3) "app is ready"
fetchData().then( response =>
setLoggedInUser( response ); // state (2) "isAuthorized" from fetch
setIsFinishedLoading( true );
).catch( error =>
setLoggedInUser( null );
setIsFinishedLoading( true );
);
, []);
useEffect(() =>
if( isFinishedLoading )
setIsAuthorized( !!response ); // state (1) "isAuthorized" in local state or Context
, [ isFinishedLoading, response ]);
“阻塞”
你可能不需要它,但是:
在这种意义上阻止代码执行在 javascript 中是不可能的。您将改为在其他时间执行代码,例如使用承诺。这再次需要以稍微不同的方式思考。
你不能这样做:
function authed()
blockExecutionBasedOnCondition
return !!loggedInUser
但你可以这样做:
function authed()
return !!loggedInUser;
function executeAuthed()
someConditionWithPromise.then( result =>
authed();
);
【讨论】:
以上是关于在另一个单独的异步调用解决之前,如何防止/锁定函数返回?的主要内容,如果未能解决你的问题,请参考以下文章