NodeJS和Electron - 后端的请求承诺冻结了前端的CSS动画

时间:2018-01-24 19:52:07

标签: javascript html css node.js electron

注意:附加信息作为编辑#1 附加到原始问题的末尾,详细说明了后端request-promise如何导致UI冻结。请记住,纯CSS动画暂时挂起,你可能只是跳到编辑(或完整阅读全部)

设置

我使用Electron在桌面网络应用上工作。

有一次,用户需要输入并提交一些数据。当他们点击"提交"时,我使用JS来显示this css loading animation(右下角加载器),并将数据异步发送到后端......

- HTML -

<button id="submitBtn" type="submit" disabled="true">Go!</button>

<div class="submit-loader">
    <div class="loader _hide"></div>
</div>

- JS -

form.addEventListener('submit', function(e) {
    e.preventDefault();

    loader.classList.remove('_hide');

    setTimeout(function() {
        ipcRenderer.send('credentials:submit', credentials);
    }, 0)
});

其中._hide只是

._hide {
    visibility: hidden;
}

ipcRenderer.send()async method,其他选项无法设置。

问题

通常0ms延迟足以允许在阻塞事件发生之前更改DOM。但不是在这里。无论是否使用setTimeout(),仍有延迟。

所以,加上一点延迟......

loader.classList.remove('_hide');

setTimeout(function() {
    ipcRenderer.send('credentials:submit', credentials);
}, 100);

大!装载机在提交后立即显示!但是......在100ms之后,动画在其轨道上停止了大约500ms左右,然后再回到chooching。

这项工作 - &gt;不工作 - &gt;无论延迟长度如何,都会发生工作模式。一旦ipcRenderer开始做事,一切都会停止。

所以...为什么!?

这是我第一次看到这种行为。我非常精通HTML / CSS / JS,但我是NodeJS和Electron的新手。为什么我的纯CSS 动画被ipcRenderer暂停,我该怎么做才能解决这个问题?

编辑#1 - 其他信息

在后端(NodeJS)中,我使用request-promise来调用外部API。当后端收到ipcRenderer消息时会发生这种情况。

var rp = require('request-promise');

ipcMain.on('credentials:submit', function(e, credentials) {    

    var options = {
        headers : {
            ... api-key...
        },
        json: true,
        url : url,
        method : 'GET'
    };

    return rp(options).then(function(data) {
        ... send response to callback...
    }).catch(function(err) {
        ... send error to callback...
    });

}

错误的冻结行为仅在第一次API调用上发生。连续的API调用(即刷新桌面应用程序而不重新启动NodeJS后端)不会导致挂起。即使我调用不同的API方法,也没有问题。

目前,我已经实施了以下hacky解决方法:

首先,使用BrowserWindow ...

初始化第一个show:false
window = new BrowserWindow({
    show: false
});

当窗口准备就绪时,向外部API发送ping,并仅在成功响应后显示窗口...

window.on('ready-to-show', function() {
    apiWrapper.ping(function(response) {
        if(response.error) {
            app.quit();
        }else {
            window.show(true);
        }
    });
});

此额外步骤意味着在窗口出现之前有大约500毫秒的延迟,但随后所有连续的API调用(无论.ping()还是其他)都不再阻止UI。我们已经到了callback hell的边缘,但这并不是太糟糕。

所以......这是一个request-promise问题(据我所知,从文档中可以看出异步)。不确定为什么这种行为只会出现在 第一次调用 上,所以如果您知道,请随时告诉我们!否则,这个小小的hacky就必须要做了。

(注意:我是唯一一个会使用此桌面应用的人,因此我并不担心显示&#34; ping失败&#34;消息。商业版本,我会提醒用户API调用失败。)

1 个答案:

答案 0 :(得分:2)

值得检查请求承诺如何在内部设置模块加载。阅读它,似乎在调用请求时有一种延迟加载(https://github.com/request/request-promise/blob/master/lib/rp.js#L10-L12)。快速试用

async function foo() {
    var outCursor = await db.Working_newFooditem.find({}).toArray()
    var inCursor = await db.Working_newFooditem.find({}).toArray()

    for (var out_c of outCursor) {
        var outUniqueString = out_c.uniqueName
        for (var in_c of inCursor) {
            var inUniqueString = in_c.uniqueName
            if (in_c === out_c) {
                await db.dupC.insert(in_c)
                await db.Working_newFooditem.remove(in_c._id)
            }
        }
    }
    return 'good job'
}

foo()
.then(msg => console.log(msg))
.catch(e => console.log(e))

返回如下所示的值:

const convertHrtime = require('convert-hrtime');

const a = require('request-promise');

const start = process.hrtime();
a({uri: 'https://requestb.in/17on4me1'});
const end = process.hrtime(start);
console.log(convertHrtime(end));

const start2 = process.hrtime();
a({uri: 'https://requestb.in/17on4me1'});
const end2 = process.hrtime(start2);
console.log(convertHrtime(end2));
第一次通话显然比后续通话时间长。 (当然可能会有所不同,我在相对较快的cpu上运行裸节点.js)如果模块加载是第一次调用的主要成本,那么它将阻止主进程直到加载模块(导致node.js {{1} } resolve is synchronous)

我不能说这是具体原因,但值得检查。正如评论中所建议的那样,尝试使用其他lib或裸内部模块(如Electron的{ seconds: 0.00421092, milliseconds: 4.21092, nanoseconds: 4210920 } { seconds: 0.000511664, milliseconds: 0.511664, nanoseconds: 511664 } )来排除。