如何避免 addEventListener 和 window.open 之间的竞争条件

How to avoid race condition between `addEventListener` and `window.open`

作为我网站 OAuth 流程的一部分,我使用 window.open 允许用户登录到第 3 方提供商,并使用 postMessage 将身份验证令牌发送回我的网页。基本流程看起来像这样,尽管我删除了与超时、清理、错误处理等相关的额外代码:

const getToken = () => new Promise((resolve, reject) => {
    const childWindow = window.open(buildUrl({
        url: "https://third-party-provider.com/oauth/authorize",
        query: {
            redirect_uri: "my-site.com/oauth-callback"
        }
    })

    window.addEventListener('message', event => {
        if(
            event.origin === window.location.origin &&
            event.source === childWindow
        ) {
            if(event.data.error) reject(event.data.error)
            else resolve(event.data)
        }
    })

    // Plus some extra code to remove the event listener and close
    // the child window.
}

基本思路是,当用户点击授权时,他们会被重定向到 my-site.com/oauth-callback。该页面在服务器端完成 OAuth 流程,然后加载包含少量 javascript 的页面,该页面仅调用 window.opener.postMessage(...)

现在,我的问题的症结在于这里实际上存在竞争条件,至少在理论上是这样。问题是 childWindow 可以假设 调用 postMessage before addEventListener 被调用。相反,如果我先调用 addEventListener,它可能会在 before 设置 childWindow 之前收到消息,我 认为 会抛出和例外?关键问题似乎是 window.open 不是异步的,所以我无法自动生成 window 设置消息处理程序。

目前,我的解决方案是先设置事件处理程序,并假设在执行该函数时消息不会进来。这是一个合理的假设吗?如果不是,是否有更好的方法来确保此流程正确?

这里不应该存在竞争条件,因为window.openwindow.addEventListener在同一个tick中同时出现,而Javascript既是单线程又是不可重入的.

如果子 window 确实设法在对 window.open 的调用完成之前发布消息,则该消息将简单地进入当前 Javascript 上下文的事件队列。您的代码仍然是 运行,因此接下来会调用 addEventListener。最后,在我们在这里看不到的某个未来时间点,您的代码 returns 控制浏览器,结束当前滴答。

一旦当前滴答结束,浏览器就可以检查事件队列并调度下一个工作(大概是消息)。您的订阅者已经到位,所以一切都很好!