2

I'm just starting out with javascript and Qt so bear with me

My issue is with the runJavaScript method. I can't seem to get the callback function to run with a returned value that's delayed. For example, the following prints None.

js = '''
function returnHello(){
    var i = 0;
    var wait = setInterval(function() { //arbitrary delay
        i++
        if(i>2){
            return('hello')
        }
    }, 10);
}
returnHello();
'''

def test(a):
    print(a)

mw.reviewer.web.page().runJavaScript(js, test)

I suspect it has something to do with how javascript runs asynchronously, and I tried playing around with javascript callback methods, but if there's any delay in returning values, the Qt python callback method always seems to accept the undefined default javascript return value.

I've been scouring the internet for answers, so any help would be great!

5
  • The return value of an asynchronous function is not used. Commented Mar 24, 2020 at 5:48
  • I want to print 'hello' in python, not javascript - how would I go about doing that Commented Mar 24, 2020 at 5:51
  • Maybe this question is related: stackoverflow.com/questions/45230931/… Commented Mar 24, 2020 at 5:58
  • That's for the suggestion, but that seems to be a different issue - all their javascript returns values without delay Commented Mar 24, 2020 at 6:08
  • Unless there's a way to pass a Python callback to runJavaScript, and have the JS code call it, I think you're out of luck. Nothing waits for an asynchronous function to finish. Commented Mar 24, 2020 at 6:10

1 Answer 1

1

Explanation:

It seems that you do not know how an asynchronous task works, an asynchronous function does not return internal information since at the time of evaluating the assignment, the task has not yet been executed nor is it blocked until it is executed. For example, the variable "wait" is a timer ID that is used to cancel the execution of the timer. Therefore runJavaScript will not work in this case.

Solution:

One possible solution is to use Qt WebChannel, based on my previous answer I have implemented the following solution:

import os
import sys
from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
from jinja2 import Template


class Element(QtCore.QObject):
    def __init__(self, name, parent=None):
        super(Element, self).__init__(parent)
        self._name = name

    @property
    def name(self):
        return self._name

    def script(self):
        raise NotImplementedError


class TestObject(Element):
    def script(self):
        _script = r"""
        function returnHello(){
            var i = 0;
            var id_timer = setInterval(function() { //arbitrary delay
                i++
                if(i>2){
                    {{name}}.test('hello')
                }
            }, 10);
        }
        returnHello();
        """
        return Template(_script).render(name=self.name)

    @QtCore.pyqtSlot(str)
    def test(self, a):
        print(a)


class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, *args, **kwargs):
        super(WebEnginePage, self).__init__(*args, **kwargs)
        self.loadFinished.connect(self.onLoadFinished)
        self._objects = []

    def add_object(self, obj):
        self._objects.append(obj)

    @QtCore.pyqtSlot(bool)
    def onLoadFinished(self, ok):
        if ok:
            self.load_qwebchannel()
            self.load_objects()

    def load_qwebchannel(self):
        file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
        if file.open(QtCore.QIODevice.ReadOnly):
            content = file.readAll()
            file.close()
            self.runJavaScript(content.data().decode())
        if self.webChannel() is None:
            channel = QtWebChannel.QWebChannel(self)
            self.setWebChannel(channel)

    def load_objects(self):
        if self.webChannel() is not None:
            objects = {obj.name: obj for obj in self._objects}
            self.webChannel().registerObjects(objects)
            _script = r"""
            {% for obj in objects %}
            var {{obj}};
            {% endfor %}
            new QWebChannel(qt.webChannelTransport, function (channel) {
            {% for obj in objects %}
                {{obj}} = channel.objects.{{obj}};
            {% endfor %}
            }); 
            """
            self.runJavaScript(Template(_script).render(objects=objects.keys()))
            for obj in self._objects:
                if isinstance(obj, Element):
                    self.runJavaScript(obj.script())


class WebPage(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, parent=None):
        super().__init__(parent)

        page = WebEnginePage(self)
        self.setPage(page)

        test_object = TestObject("test_object", self)
        page.add_object(test_object)

        self.load(QtCore.QUrl("https://stackoverflow.com/"))


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    web = WebPage()
    web.show()
    sys.exit(app.exec_())
Sign up to request clarification or add additional context in comments.

1 Comment

Ah yes this makes a lot of sense - thank you for the explanation and the example

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.