惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
Y
Y Combinator Blog
Microsoft Security Blog
Microsoft Security Blog
宝玉的分享
宝玉的分享
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
Jina AI
Jina AI
D
Docker
B
Blog
Engineering at Meta
Engineering at Meta
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
I
InfoQ
G
Google Developers Blog
博客园 - Franky
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
阮一峰的网络日志
阮一峰的网络日志
U
Unit 42

JavaScript

发现国内很多文章对宏任务和微任务的解释都是有问题的 智能香氛机小程序外包合作招募 分享一道 Node.js 的面试题,考察 JS 相关的最基本的掌握程度,代码完全来自官方文档 你们 js 用过双等号吗 有人大量建立技术文档的.cn 中文站点,并将站内 js 链接指向 cdn.jsdelivr.net.cn 发现了两个关于 JavaScript 语言好玩的网站,让人血压升高 js 判空值 最佳实践 简单的图像算法为什么 AI 搞不定呢 Capacitor.JS 如何自动化最后一步流程 JSON.parse 转数字精度丢失问题 有没有前端大佬,帮我看一下这个网站底部的粒子动画怎么做的。 nstr - number → string, but looks good 前端 js 播放 base64 编码的 mp3,放出来杂音很大,请教是哪里出错了? 熟悉工业计算的来,是阀门相关的企业 js 不使用 promise 完成一个 sleep 函数,必须支持 await 调用 小米手机澎湃系统 AutoJS 的悬浮窗在证券软件打开时就消失 关于 js 复制粘贴问题 求助,无法在 wixsite.com 上使用 document.write threejs 官方文档看着好难受。 有没有 AI 能一步一步教会伸手党如何使用业务系统? 也聊下自己最近用 copilot 写程序的心得,还要讨教下方案 javascript 继续 套壳 AI 应用类 manus 如何在移动端 h5 页面实现<Select/>搜索下拉选择框! 求前端高频数据绘图库 吐槽一下 Trae 记忆中好像在论坛看到有款专门在 iframe 中做前端的 js 库/框架? JavaScript 中的 Promise 跟异步有关系吗?还是我的理解有问题?谁能把 Promise 解释清楚? Promise 的正确用法应该是什么样的? 求问,小程序的菜单栏怎么和右边的功能键垂直居中? 自习室小程序的座位图是如何实现的(前端) 谷歌浏览器的 bug
这段前端代码存在并发读取竞态的问题吗?
supuwoerc · 2025-09-04 · via JavaScript

这两天调试别人项目中的一段 js 代码,作用是刷新 token ,但是验证下来发现有很小的几率会触发多次刷新 token 的动作(下面代码中的 FIXME 位置),特别是 Promise.all 去发送一批请求的时候,我 google 了一圈,没研究明白,因为复现起来很困难,所以请教大家,代码中读取 isRefreshing 是安全的吗?我让 cursor 和 copilot 解释都是说 js 不启用 worker 是不存在并发问题的,但是从结果来看,确实有不止一个请求进入了刷新 token 的分支,我把这个情况描述完,cursor 让我引入 sync-mutex 加锁,和一开始的解释完全不一样,我在 StackOverflow 和 medium 中也找到几篇类似的文章,都是借助了防抖/记忆函数来解决,实在弄不清楚这块读写 isRefreshing 到底是不是安全的。

还看到了一篇锁的文章,感觉很类似我遇到的这个问题: https://jackpordi.com/posts/locks-in-js-because-why-not

伪代码如下:

let isRefreshing = false // 标记是否正在刷新 token 
let requests: Array<(token: string, err?: string) => void> = [] // 需要重试的请求列表

client.interceptors.response.use((response: AxiosResponse) => {
            const { config, status } = response
            const { code } = response.data
            if (status >= 500) {
                return Promise.reject("服务器错误")
            } else if (code == 10003) {
                // access token 过期,尝试刷新 token
                const { refreshToken } = user.useLoginStore.getState()
                if (refreshToken) {
                    // FIXME: ?? 存在并发读取 isRefreshing 为 false 导致发出多次刷新 token 的请求
                    if (!isRefreshing) {
                        isRefreshing = true
                        return refreshToken()
                            .then(({ data }) => {
                                const { code } = data
                                if (code === 10000) {
                                    user.useLoginStore.setState((state) => {
                                        state.token = data.data.token
                                    })
                                    config.headers["Authorization"] = data.data.token
                                    const retry = client(config)
                                    requests.forEach((cb) => cb(data.data.token))
                                    requests = []
                                    return retry
                                } else {
                                    return Promise.reject(data.message)
                                }
                            })
                            .catch((err) => {
                                const msg = isError(err) ? err.message : err
                                requests.forEach((cb) => cb("", msg))
                                requests = []
                                publishInvalidTokenEvent(msg)
                            })
                            .finally(() => {
                                isRefreshing = false
                            })
                    } else {
                        return new Promise((resolve, reject) => {
                            requests.push((token: string, err?: string) => {
                                if (err) {
                                    reject(err)
                                } else {
                                    config.headers["Authorization"] = token
                                    resolve(client(config))
                                }
                            })
                        })
                    }
                } else {
                    requests.forEach((cb) => cb("", "登录过期")
                    requests = []
                    publishInvalidTokenEvent("登录过期")
                }
            } else if (code === 10000) {
                return response.data.data
            } else if (code == 10006) {
                // 长 token 失效
                requests.forEach((cb) => cb("", "登录过期")
                requests = []
                publishInvalidTokenEvent("登录过期")
            } else {
                return Promise.reject(response.data.message || response.data.msg)
            }
        })