如何避免addEventListener和window.open之间的竞争状态

时间:2018-07-09 17:17:02

标签: javascript oauth promise race-condition postmessage

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

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的页面,这些javascript简单地调用了window.opener.postMessage(...)

现在,我的问题的症结在于,至少在理论上,这里确实存在种族问题。问题是childWindow可以在调用addEventListener之前假设地调用postMessage 。相反,如果我先呼叫addEventListener,它可能会在设置了{em> childWindow之前收到消息,我认为会抛出该异常?关键问题似乎是window.open不是异步的,所以我不能自动生成窗口 并设置消息处理程序。

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

1 个答案:

答案 0 :(得分:1)

这里不应该存在争用条件,因为window.openwindow.addEventListener一起出现在同一滴答中,并且Javascript既是单线程的又是不可重入的。

如果子窗口确实在对window.open的调用完成之前成功发布了一条消息,则该消息将直接进入当前Javascript上下文的事件队列。您的代码仍在运行,因此接下来发生addEventListener调用。最后,在我们这里看不到的将来,您的代码将控制权返回给浏览器,从而结束了当前的滴答声。

当前滴答结束后,浏览器可以检出事件队列并调度下一部分工作(可能是消息)。您的订户已经就位,所以一切都很好!