Electron 实现循环网络连接判断及定时器分析

Electron 判断网络环境

请参考: Electron 判断网络环境

安装依赖

npm install qiao-is-online -D
npm install time-format-tools -D

对应 npm 仓库地址: npm time-format-toolsnpm 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 ,请看我后续发布的文章。

更多文章

更多可以参考其他文章,我给出我看到的一些文章:

posted @ 2021-07-15 17:25  Yogile  阅读(835)  评论(0编辑  收藏  举报