Electron 实现循环网络连接判断及定时器分析
Electron 判断网络环境
请参考: Electron 判断网络环境 。
安装依赖
npm install qiao-is-online -D
npm install time-format-tools -D
对应 npm 仓库地址: npm time-format-tools 、 npm qiao-is-online 。
定时器
推荐使用 setTimeOut()
进行递归。
对于两个函数都需要获取 ID 进行回收(按钮监听触发等等)。
setInterval()
/*
* @Author: Yogile
* @Gitee: https://gitee.com/yogile
* @Date: 2021-07-13 17:54:59
* @LastEditors: Yogile
* @LastEditTime: 2021-07-14 14:29:52
* @Description: setInterval() 循环实现 ping
*/
/**
* 引入包
*/
var q = require('qiao-is-online');
var timeFT = require('time-format-tools');
/**
* 声明全局变量 onLine
* @description: 由于 Promise.then() 内的变量值不使用 async/await 等一般无法取出,
* 提供全局变量,提供 then() 内的 onLine 引用
*/
var onLine = null;
/**
* @name: getFormatTime()
* @description: Get format time
* @return {string}
*/
function getFormatTime() {
return timeFT('YYYY-MM-DD hh:mm:ss');
};
/**
* @name: onlineIterator()
* @description: 作为 setInterval() 函数的参数函数
*/
function onlineIterator() {
// try-cache 对错误进行捕获
try {
// 获取 async 方法返回的 Promise 值赋予 isOnline
var isOnline = q.isOnline();
// 由于 async 方法返回的内容在 Promise 值内的为 PromiseResult ,
// 不使用 async/await 等一般无法取出,将和 PromiseResult 有关的逻辑包含在 Promise.then() 函数内
isOnline.then(function(result) {
// 初始化局部变量,与全局变量 onLine 不同,在函数递归的过程中,局部变量的值不会继承
let flag = false;
var opetion = {
title: '底部通知消息',
body: '底部通知消息内容'
};
// 判断 PromiseResult 的值
if (result === 'online') {
flag = true;
}
// 判断互联网连接情况是否发生变化,变化则通知
if (onLine != flag) {
// 变更本次互联网连接情况
onLine = flag;
// 设置通知信息
opetion.title = "Electron 网络变化通知";
if (onLine == true) {
opetion.body = "互联网已连接";
flag = "online" + " " + getFormatTime();
} else {
opetion.body = "互联网已断开";
flag = "offline" + " " + getFormatTime();
}
// 弹出底部通知消息
new window.Notification(opetion.title, opetion);
console.log("[Network] " + flag);
}
// 释放局部变量引用,脱离执行环境,在下一次垃圾收集器执行操作时被找到并释放。
flag = null;
opetion = null;
});
} catch (e) {
console.log("[Network] error" + " " + getFormatTime());
console.log(e);
clearInterval(id_onlineIterator);
}
};
var id_onlineIterator = setInterval(onlineIterator, 10 * 1000);
setTimeout()
迭代
/*
* @Author: Yogile
* @Gitee: https://gitee.com/yogile
* @Date: 2021-07-13 17:54:59
* @LastEditors: Yogile
* @LastEditTime: 2021-07-14 14:17:24
* @Description: setTimeOut() 递归实现 ping
*/
/**
* 引入包
*/
var q = require('qiao-is-online');
var timeFT = require('time-format-tools');
/**
* 声明全局变量 onLine
* @description: 由于 Promise.then() 内的变量值不使用 async/await 等一般无法取出,
* 提供全局变量,提供 then() 内的 onLine 引用
*/
var onLine = null;
/**
* @name: getFormatTime()
* @description: Get format time
* @return {string}
*/
function getFormatTime() {
return timeFT('YYYY-MM-DD hh:mm:ss');
};
/**
* @name: onlineIterator()
* @description: 通过 setTimeout() 进行递归
*/
function onlineIterator() {
// try-cache 对错误进行捕获
try {
// 获取 async 方法返回的 Promise 值赋予 isOnline
var isOnline = q.isOnline();
// 由于 async 方法返回的内容在 Promise 值内的为 PromiseResult ,
// 不使用 async/await 等一般无法取出,将和 PromiseResult 有关的逻辑包含在 Promise.then() 函数内
isOnline.then(function(result) {
// 初始化局部变量,与全局变量 onLine 不同,在函数递归的过程中,局部变量的值不会继承
let flag = false;
var opetion = {
title: '底部通知消息',
body: '底部通知消息内容'
};
// 判断 PromiseResult 的值
if (result === 'online') {
flag = true;
}
// 判断互联网连接情况是否发生变化,变化则通知
if (onLine != flag) {
// 变更本次互联网连接情况
onLine = flag;
// 设置通知信息
opetion.title = "Electron 网络变化通知";
if (onLine == true) {
opetion.body = "互联网已连接";
flag = "online" + " " + getFormatTime();
} else {
opetion.body = "互联网已断开";
flag = "offline" + " " + getFormatTime();
}
// 弹出底部通知消息
new window.Notification(opetion.title, opetion);
console.log("[Network] " + flag);
}
// 释放局部变量引用,脱离执行环境,在下一次垃圾收集器执行操作时被找到并释放。
flag = null;
opetion = null;
});
// 在继续递归调用之前,通过 setTimeout() 返回的 id 释放原句柄,防止堆栈溢出
clearTimeout(id_onlineIterator);
id_onlineIterator = null;
// 继续递归
id_onlineIterator = setTimeout("onlineIterator()", 10 * 1000);
} catch (e) {
console.log("[Network] error" + " " + getFormatTime());
console.log(e);
// 通过 setTimeout() 返回的 id ,结束递归
clearTimeout(id_onlineIterator);
id_onlineIterator = null;
}
};
// 获得 setTimeout() 返回的 id ,用于 clearTimeout()
var id_onlineIterator = setTimeout("onlineIterator()", 10 * 1000);
相似与区别
可以提供相似的功能
setTimeout(function, milliseconds, param1, param2, ...)
只在规定的 milliseconds
执行一次函数。如果要多次调用,请使用 setInterval()
或者让 function
自身再次调用 setTimeout()
。
两者具有基本相似的执行性能。
执行逻辑不同
-
使用
setInterval()
的问题在于,对于创建的定时器中的任务是定时添加的,添加的任务只与时间有关,与其他任务执行情况一般无关。定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。这里有一段代码可以具体表现这个问题。
var id_setInterval = setInterval(function() { alert("setInterval"); }, 5000);
该段代码每 5000 毫秒触发一次
alert("setInterval");
,如果在setInterval()
触发 15000 毫秒后再去点击弹出的alert
通知,就会发现需要连续点击三次 “确定” 才能关掉弹窗,进入空窗期。我在查找资料中发现,有人(为什么尽量别用setInterval)总结了几点:
setInterval()
无视代码错误。setInterval()
无视网络延迟。setInterval()
不保证执行。
setInterval()
是异步的,但同时 JavaScript 是单线程的,因此如果正在执行大量在任务中工作,它在 30 秒内运行一次可能需要很长时间才能执行从而阻塞资源。 -
而通过
setTimeout()
迭代进行,则是在每次点击 “确定” 后,由新的setTimeout()
创建新的定时器,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的间隔;并保证在下一次定时器代码执行之前,至少要等待指定的间隔,避免了连续的运行。function alertInside() { alert("setTimeout"); setTimeout("alertInside()", 5000); } var id_setTimeout = setTimeout("alertInside()", 5000);
该段代码同样也是每 5000 毫秒触发一次
alert("setTimeout");
,如果在var id_setTimeout = setTimeout("alertInside()", 5000);
触发 15000 毫秒后再去点击弹出的alert
通知,就会发现还是只需要点击一次 “确定” 就能关掉弹窗,进入空窗期。
回收方式不同
Node v8 引擎有自动回收垃圾并释放当前窗口的栈内存的机制,但这个自动回收仅限于当变量的指针指向 null 时,才将变量不再使用的对象从栈空间及时回收,否则它会在窗口对象销毁时才回收。
-
对于
setInterval()
来说,只有创建时获取的 ID 被关闭,定时器才关闭。clearInterval(id_onlineIterator);
如果 ID 为全局变量,无论在
setInterval()
调用的函数内部,还是在外部由按钮监听触发,都能关闭定时器,否则不会回收。如果调用的次数过多,定时器的队列超长,会发生堆栈溢出导致内存泄漏。
-
对于
setTimeout()
来说,每次调用setTimeout()
都会创建一个新的定时器,该定时器在执行完后会被回收。但是,原有的句柄释放虽然会被GC回收,而时间不确定,这样做比较危险。在
setTimeout()
迭代内部,是无法使用clearXxxx()
结束setTimeout()
的。clearTimeout(id_onlineIterator);
注意,无论是 setInterval()
还是 setTimeout()
,在执行了 clearXxxx()
命令后,ID 变量的值并不会改变,关闭的只是 ID 对应的句柄,所以应该在 clearXxxx()
之后,手动置 ID 变量为 null 。
关于
时间漂移
JavaScript的计时器并不是非常精确。因此你不可能得到绝对“平均”的延迟,即使使用 setInterval()
也不行。原因很多(比如垃圾回收、JavaScript是单线程的,等等)。此外,当前浏览器也会将最小的超时时间固定在4ms到15ms之间。因此不要指望一点误差也没有。
如果确实要保证事件尽可能匀速被触发,那可以用希望的延迟减去上次调用所花时间,然后将得到的差值作为延迟动态指定给 setTimeout()
。
测试
Windows 下,通过在 网络连接
中启用或禁用连接互联网的网络,可以达到断网、联网的区别(Linux 同理)。
结果
执行结果如下:
[Network] online 2021-07-14 14:11:17 network.js:76
[Network] offline 2021-07-14 14:11:47 network.js:76
[Network] online 2021-07-14 14:11:57 network.js:76
内存占用
时间 | Electron 内存 |
---|---|
2021-7-15 15:39:20 | 144.7 MB |
2021-7-15 15:40:20 | 148.7 MB |
..... | ...... |
2021-7-15 16:10:20 | 147.5 MB |
可以看到在 setTimeout()
迭代运行时,内存的变化并不大(当然只是在任务管理器中看看)。
遇到的问题
format()
当我使用之前的 new Date.format()
时,发现弹出以下错误:
(intermediate value).format is not a function
查阅资料后发现是 JavaScript 丢弃了此方法。
从 Promise 获取数据
当我使用到 qiao-is-online
中的 isOnline()
的时候,我发现 isOnline()
的返回值并不像函数中描述的字符串,反而返回了一个我没有见过的变量类型 Promise 。然后我通过 console.log()
打印出了结果,发现我需要的字符串信息就在其中。
Promise {<pending>}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "online"
没有怎么学过 JS 的我就下意识的去想,这个变量类型作为一个结构体,那么就一定有一种方法获取对应的值。于是,我失败了。
在我去尝试使用 async/await 去取值的时候,我看到了一篇文章:[有没有办法将 [Promise Result]] 保存为变量? 。
其中有一段代码,将我的思想从 “要将参数从Promise取出赋值” 转换成了 “通过 Promise 本身传递信息” 。
function currentloginid() { return fetch('https://jsonplaceholder.typicode.com/users/1') .then(response => response.json()) } const storedPromise = currentloginid(); console.log('No delay') storedPromise.then(({name, id})=> console.log({name, id})) setTimeout(()=>{ console.log('After delay, same promise') storedPromise.then(({name, id})=> console.log({name, id})) }, 2000)
- 当然我后续会总结一下我理解的 Promise ,请看我后续发布的文章。
更多文章
更多可以参考其他文章,我给出我看到的一些文章: