在网上浏览的时候看到了下面的一种写法,可以通过新建一个 Promise 对象,在 Promise 对象中绑定回调函数,并获取回调结果,类似与下面这种:
const img = document.getElementById("bg");
const message = () => {
return new Promise((resolve, reject) => {
img.onload = () => {
resolve("Image loaded successfully"); // 返回回调函数的结果
};
});
};
const AsyncTest = async () => {
try {
const msg = await message();
console.log(msg);
} catch (error) {
console.log(error);
}
};
上面的代码确实能够将回调函数的返回值作为 Promise 的调用结果返回。
随后突发奇想,用下面这种方式分别异步调用两次上面代码中的 AsyncTest 的函数:
// 连续两次调用 AsyncTest 函数
AsyncTest().then(() => {
console.log("Async function executed");
});
AsyncTest().then(() => {
console.log("Async function executed");
});
结果发现虽然两次调用,但是只有一个调用有返回,个人的理解是:
这样是否就意味着,第一次的 Promise 始终没有完成,保存在栈空间中,并在 EventLoop 中循环等待 resolve 的调用。 在图片加载完成后拍了堆快照,发现堆中确实留下了一个 Promise 的空间:
请教一下,这是否意味着这块内存空间是否会始终被占用,以及利用 Promise 监听 onload 回调这种写法是否妥当?
1
xiaoming1992 196 天前 via Android
用 addEventListener ,在 load 及 error 及 aborted 中 removeEventListener ,这样应该 OK
|
2
ysc3839 196 天前
感觉上会被 GC 掉,因为第一个 onload 被覆盖掉后就没有对象引用第一个 Promise 了。
实际如何我说不清。 |
3
Al0rid4l 196 天前
https://www.zhihu.com/question/627670924/answer/3270699180
另外不建议 Promise 监听事件, 因为事件可能返回多个值(多次触发), 而 Promise 只有一个值, 事件用 Observable(Rx) |
4
RedNax 196 天前
理论上会被 GC 回收掉。
第一个 Promise new Promise((resolve, reject) => { img.onload = () => { resolve("Image loaded successfully"); // 返回回调函数的结果 }; }); 不被 GC 是依赖 resolve 和 reject 的引用 然而因为有第二个 Promise ,img.onload 不再 hold 这个函数: () => { resolve("Image loaded successfully"); // 返回回调函数的结果 }; 那么函数里面的 resolve 也相当于没有被引用了。 这样 GC 就知道这个 Promise 是可以安全 GC 的。 |
6
ashong 196 天前 via iPhone 3
第一次调用后 img 的 onload 已经完成,第二次的 promise 不会 resolve
|
7
jchonc 196 天前
我也猜楼上是对的,如果 AsyncTest 把 Message 加成参数再试试?
|
8
Kaciras 196 天前
没有引用的 Promise 会被 GC ,你可以搞个循环创建,然后在内存工具里看到锯齿状的图。
|
9
jazzg62 196 天前
promise 在 onload 中 resolve 是没有问题的,只是你写的这个代码有点问题,img 的 onload 函数只会触发一次,所以,也只会有一次 resolve 。
你需要使用其他手段来判断 img 是否加载完成,如果没有加载完成,才绑定 onload 函数。 一个不能 resolve 的 promise 确实会占用内存,会在事件循环一直等待处理 ```javascript const message = () => { return new Promise((resolve, reject) => { if (img.complete) { resolve("Image loaded successfully"); // 返回回调函数的结果 return ; } img.onload = () => { resolve("Image loaded successfully"); // 返回回调函数的结果 }; }); }; ``` |
11
xiangyuecn 196 天前
没错,是你理解的这样。很简单就是 onload 被第二个给覆盖了
new Promise() 是同步方法,没错 是同步🐶 |
12
jguo 196 天前 1
你说的这一大串跟 promise 没什么关系。element.onload=handler 这种写法早就不推荐了,应该用 addEventListener 。
|
13
lolizeppelin 195 天前
别折腾了 用 rx !
|
14
wuoty OP |
15
wuoty OP |
16
monokuma88 195 天前
一个不负责任的优化方案(以能用就行为原则):
const message = () => { return new Promise((resolve, reject) => { if (img.complete) { resolve("Image loaded successfully"); return; } const prevOnload = img.onload; img.onload = () => { if (prevOnload) { prevOnload(); } resolve("Image loaded successfully"); // 返回回调函数的结果 }; }); }; |
18
abc1310054026 195 天前
其实关键在于不要出现无法“resolve”的 Promise 对象。
在你这种场景下就是尽量使用 addEventListener 而不是 onXXX 。因为 onXXX 容易不小心被覆盖。 |
19
07aPzknB16ui9Cp3 195 天前
这就是单纯的 onload 只会执行一次而已,跟你的 Promise 也没什么关系,虽然这么写很奇葩就是了
|
20
lmshl 195 天前 1
```
import { fromEvent } from 'rxjs'; const clicks = fromEvent(img, 'onload'); clicks.subscribe(x => { <你的业务逻辑> }); ``` 不要自作聪明发明那些不可维护的代码,你能想得到的场景,早已有无数前辈替你趟过坑。 等你哪天觉得 “RxJS 不过如此,我还有个更好的想法” 的时候,再发明也不迟(顺便发几篇论文) |
21
amlee 195 天前
真别 rxjs 一直说了,原生 api 就有解决方案,就是 addeventlister 添加事件触发函数,防止重复绑定,建议读读这篇
https://zh.javascript.info/introduction-browser-events#addeventlistener |
22
enjoyCoding 195 天前 1
这种带有副作用的函数(绑了时间没有解绑)调一次可以, 调两次不行. 因为元素是一个 onload 时间只会在第一次触发, 甚至如果不立刻调用都不会触发. 正常的操作应该写成无副作用的函数, 怎么改成无副作用的函数 每次都新建一个插入 body, 绑定有 addEvent 用完了解绑, 删除元素
|
23
Al0rid4l 195 天前
@RedNax 理论上来讲一次性事件当然是可以, 不过个人觉得这个习惯不好, 比如写习惯了一下没注意结果什么事件都用 Promise, 或者同事看见了以为用 Promise 把事件转 awaitable 是正确操作, 把其他事件也跟着这么写, 都很容易埋坑
|
24
jerry4718 195 天前 via Android
都是说的什么啊,问题明明出在 onload 被覆盖上面啊
|
25
jerry4718 195 天前 via Android
抱歉,眼瞎了
我觉得用 Promise 来监听 onload 合不合理要分场景来说 如果 img 生命周期很短,比如说封装的工具函数,直接 create 一个 img ,在 onload 里面获取 blob 或者尺寸信息,这时候会不会被覆盖是可控的,个人认为也可以视作“小而美”的 但是如果生命周期比较长,emmmm ,那不好说 ,有可能写出来的逻辑都不能符合预期,副作用这东西,要多讨厌有多讨厌 |
26
nexo 195 天前 via iPhone
@lolizeppelin rx 是什么
|
27
xiyuesaves 195 天前
@nexo #26 https://rxjs.dev/
|