各位 JS 高手你好,问一个关于 for 循环使用$.post 替换全局变量的问题,好像是竞争锁之类的

2024-03-18 10:40:31 +08:00
 abccccabc
各位 JS 高手你好,问一个关于 for 循环使用$.post 替换全局变量的问题,好像是竞争锁之类的。
```
var allimg = 获取到的图片数组;
var oldcontent = 原内容;
for(var i=0; i<allimg.length; i++){
$.post('url', 参数, function(ret) {
if(ret['code'] == 200) {
oldcontent = oldcontent.replace(ret['oldimgurl'], ret['newimgurl']);
}else{
console.log(错误信息);
}
}, 'json');
}
```
这样有一个很大的问题:多个异步去修改同一全局变量,必须要锁定全局变量 oldcontent ,不然只有最后 i 循环 修改 oldcontent 生效。
请高手支招?
7239 次点击
所在节点    JavaScript
83 条回复
qrobot
2024-03-18 14:51:34 +08:00
@Plumbiu ecmascript 只是把任务交给浏览器去执行, 但是未必固定了任务是有序的, 和浏览器的实现有关, 虽然 w3c 固定了任务序列. 但是实际上执行上是有差异的. 例如 edge 的节能模式下 setTimeout 执行上就存在问题
Plumbiu
2024-03-18 14:53:09 +08:00
@qrobot 我也有点不确定啊,感觉如果是两个请求在队列里,前面不知道会不会存在队头阻塞的情况,应该是你说的对,post 接口返回的时间不一样,可能执行顺序不同
crz
2024-03-18 14:58:16 +08:00
看起来是 replace 的问题?

1. 对比每次 repalce 前后的 oldcontent
2. 查看回调的 ret 值
qrobot
2024-03-18 14:59:20 +08:00
@Plumbiu 不说 XMLHttpRequest 这点时间不同执行顺序不同, 都不用考虑, 但是异步任务中 js 的异步队列中并不是按照 按照顺序执行的. 据我所知的在 edge 和 chome 上都有不同的差异, 在 firefox 上也有差异, 你不能信任 ecmascript 的异步队列.
qrobot
2024-03-18 15:01:25 +08:00
@Plumbiu 以前我验证过 setTimeout 在部分情况下会直接被优化, 包括 setInterval 优化的策略根据浏览器的版本也有所区别. 和 w3c 规定不太一样
persimmon
2024-03-18 15:18:50 +08:00
首先这段代码会把所有同步部先执行一遍,所有的 $.post 都会第一时间执行,然后根据 $.post 请求的返回时间依次调用 callback 也就是说尽管 oldcontent 会经历多次修改,最终结果还是由最后返回的 $.post 请求结果决定
wOuv7i4e7XxsSOR1
2024-03-18 15:22:27 +08:00
没眼看
ns09005264
2024-03-18 15:30:42 +08:00
你问题里的代码没有问题啊, oldContent 最终都会被正确替换呀。
有问题的是 for 循环的 var i ,但是你代码里也没有使用,除非你隐藏了和索引 i 相关的代码。
```
var str = "Hello ";
for (var i = 0; i < 5; i++) {
wait().then(() => {
str = str + "i";
console.log("index: ", i, "str: ", str);
});
}
async function wait() {
return new Promise((resolve) => {
const time = Math.floor(Math.random() * 100 + 100);
setTimeout(resolve, time);
});
}
```
这段代码和你的基本一样,输出是这样的:
```
index: 5 str: Hello i
index: 5 str: Hello ii
index: 5 str: Hello iii
index: 5 str: Hello iiii
index: 5 str: Hello iiiii
```
也就是说 var i 类似全局变量

如果把 var i 换成 let i ,输出是这样的:
```
index: 1 str: Hello i
index: 4 str: Hello ii
index: 2 str: Hello iii
index: 0 str: Hello iiii
index: 3 str: Hello iiiii
```
每次 post 完成后的回调都能正确获得自己的索引。
persimmon
2024-03-18 15:33:17 +08:00
@abccccabc 空白太久的问题,不如通过 promise.all full resolved 之前给全页面加个 loading 来解决
clue
2024-03-18 16:51:56 +08:00
和锁没关系, JS 是单线程的, 代码中的 oldcontent 在所有请求完成后也一定是对的 ( 当然你没有用 replaceAll, 在有重复 url 时会有问题 )

你的问题不是 oldcontent 没生效, 而是 oldcontent 在异步变更后你的 html 没有同步更新才对

解决的办法
- 用现成框架, vue / mobx 的响应式, oldcontent 变更后自动重新渲染到 html
- 每次请求完成后, 主动更新一次 oldcontent 到 html
flybluewolf
2024-03-18 19:10:31 +08:00
这是 JS 最大特点,异步调用不会阻塞同步代码运行,等异步调用完成后代码运行切换到 callback 上,这是你的 oldcontent 被赋值,由于异步调用不可预测性,你无法知道哪次调用是最后结束的,也是说你的 oldcontent 最后结果是随机的。
解决方案:
1. 使用 promise 或 async/await 。
2. callback 方式可使用 async 库, https://github.com/caolan/async
flybluewolf
2024-03-18 19:11:47 +08:00
遗漏,
3. 利用闭包
abccccabc
2024-03-18 22:42:36 +08:00
我的天呐,高手们这么热情。

写了两遍实现。太浪费时间了。

@ming159 39L 这个太简单了,早点看到这个就好了。一直在想着因为异步导致的问题,结果把大家都带偏了。

已经实现了,谢谢各位
abccccabc
2024-03-18 22:57:23 +08:00
有个问题我觉得挺奇怪的,为啥有些高手盯着变量 i 呢?
我原有代码(doReplace 函数里的代码写在 for 循环内)和 39L 的基本一模一样。他只是提取$.post 到 doReplace 函数里,就成功了。神奇之术。难道是因为变量的作用域问题吗?
ZztGqk
2024-03-19 01:10:47 +08:00
@abccccabc #72 用 let 别用 var 声明 i ,不然传进去的 url 是一模一样的。
ZztGqk
2024-03-19 01:11:54 +08:00
@abccccabc 2024 年了,请多用 let 和 const ,可以避免很多问题。
ns09005264
2024-03-19 02:09:24 +08:00
@abccccabc
我重新确认了下,你问题里的代码没问题啊,也不用提取到循环外的函数里,除非在 post 回调函数里使用了 var i 变量,把关键的问题代码隐藏了。
```
var allimg = ["https://www.baidu.com/s/1.jpg", "https://www.baidu.com/s/2.jpg", "https://www.baidu.com/s/3.jpg", "https://www.baidu.com/s/4.jpg"];
var oldcontent = "<img src='https://www.baidu.com/s/1.jpg'><img src='https://www.baidu.com/s/2.jpg'><img src='https://www.baidu.com/s/3.jpg'><img src='https://www.baidu.com/s/4.jpg'>";
for (var i = 0; i < allimg.length; i++) {
post(allimg[i], function(ret) {
oldcontent = oldcontent.replace(ret['oldimgurl'], ret['newimgurl']);
console.log(oldcontent);
});
}
function post(url, callback) {
const time = Math.floor(Math.random() * 100 + 100);
setTimeout(() => callback({"oldimgurl": url, "newimgurl": url + "new"}), time);
}
```
输出:
```
<img src='https://www.baidu.com/s/1.jpgnew'><img src='https://www.baidu.com/s/2.jpgnew'><img src='https://www.baidu.com/s/3.jpgnew'><img src='https://www.baidu.com/s/4.jpgnew'>
```
https://runjs.co/s/IUEhsmWc2
alleluya
2024-03-19 09:02:29 +08:00
@qrobot js 也没有并发任务这种说法吧...
juntaol678
2024-03-19 10:39:32 +08:00
@wu67 哈哈,一眼前端做题人
hellofreckles
2024-03-19 11:23:18 +08:00
from gpt35:

async function replaceImages(allimg, oldcontent) {
for (let i = 0; i < allimg.length; i++) {
try {
const ret = await $.post('url', 参数, 'json');
if (ret.code === 200) {
oldcontent = oldcontent.replace(ret.oldimgurl, ret.newimgurl);
} else {
console.log('错误信息');
}
} catch (error) {
console.error('请求失败:', error);
}
}
return oldcontent;
}

const allimg = ['https://www.baidu.com/s/abc.jpg', 'https://www.csdn.com/s/123.jpg', 'https://www.jd.com/s/mn.jpg'];
const oldcontent = "我是文章内容,里面有多个远程图片,如<img src='https://www.baidu.com/s/abc.jpg'> <img src='https://www.csdn.com/s/123.jpg'> <img src='https://www.jd.com/s/mn.jpg'>,甚至有些文章内容的图片会更多,现在的需求就是将这些文章内容中的图片进行本地化。替换掉文章内容远程图片,最后入库";

replaceImages(allimg, oldcontent)
.then((newContent) => {
console.log('替换后的内容:', newContent);
// 在这里可以将新内容入库等操作
})
.catch((error) => {
console.error('替换图片失败:', error);
});

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1024619

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX