Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS Async Function in QML

Tags:

javascript

qml

I'm wondering if it is possible to use an async function in QML.
Like this:

async function additem(clientName){
    myListModel.append({name:clientName});
}

Instead of using this:

function additem(clientName){
    myListModel.append({name:clientName});
}

I've tried it and I get a syntax error: Expected token:'` and couldn't find any documentation about QML supporting JavaScript "async function".

any idea why is that? or probably if there is any other way to do async in JS which can be used in QML?

Note: I want to append data to my ListModel and I want to see it as its progress and I don't want whole application freezes until whole data appended to the model.

Appreciate your time.

like image 214
Bear Avatar asked Jul 27 '26 01:07

Bear


1 Answers

Even in Qt 6 async/await is not present. Here's the open QTBUG bug/enhancement issue https://bugreports.qt.io/browse/QTBUG-58620

However, since Qt5.12 we have Promises chaining.

First, here's an example of simple Promise-chaining:

Button {
    text: qsTr("Add Names")
    enabled: !busy
    onClicked: Promise.resolve()
        .then( () => busy = true )
        .then( () => listModel.clear() )
        .then( () => listModel.append( { name: "Bill Gates" } ) )
        .then( () => new Promise(resolve => setTimeout(resolve, 1000) ) )
        .then( () => listModel.append( { name: "Steve Jobs" } ) )
        .then( () => new Promise(resolve => setTimeout(resolve, 1000) ) )
        .then( () => listModel.append( { name: "Jeff Bezos" } ) )
        .then( () => busy = false )
}

[Edit Jan 2026 - Added extended Promise-chaining answer]

Here's an extended example of Promise-chaining. We use this pattern in our production code because it follows a well-established Promise-chaining approach while still allowing you to insert conditional logic-for example, adding or removing steps dynamically based on runtime conditions:

Button {
    text: qsTr("Add Names")
    enabled: !busy

    property var chain: Promise.resolve()

    onClicked: {
        chain = chain.then(() => busy = true)
        chain = chain.then(() => listModel.clear())
        chain = chain.then(() => listModel.append({ name: "Bill Gates" }))
        chain = chain.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
        chain = chain.then(() => listModel.append({ name: "Steve Jobs" }))
        chain = chain.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
        chain = chain.then(() => listModel.append({ name: "Jeff Bezos" }))
        chain = chain.then(() => busy = false)
    }
}

You can Try this online!

[Edit Jan 2026 - Removing the _asyncToGenerator() version of my answer. We never use it in production code and I don't recommend you to use it either]

Next, here's the same example written as a generator function with yield on Promises:
Button {
    text: qsTr("Add Names")
    enabled: !busy
    onClicked: _asyncToGenerator(function*() {
        busy = true;
        listModel.clear();
        listModel.append( { name: "Bill Gates" } );
        yield new Promise(resolve => setTimeout(resolve, 1000));
        listModel.append( { name: "Steve Jobs" } );
        yield new Promise(resolve => setTimeout(resolve, 1000));
        listModel.append( { name: "Jeff Bezos" } );
        busy = false;
    })()
}

Then, we refactor the above into multiple generator functions for where you may have had multiple async functions:

Button {
    text: qsTr("Add Names")
    enabled: !busy
    property var sleep: function*(delay) {
        yield new Promise(resolve => setTimeout(resolve, delay));
    }
    property var addname: function*(name) {
        yield listModel.append( { name: name } );
    }
    property var addnames: function*() {
        busy = true;
        listModel.clear();
        yield* addname("Bill Gates");
        yield* sleep(1000);
        yield* addname("Steve Jobs");
        yield* sleep(1000);
        yield* addname("Jeff Bezos");
        busy = false;
    }
    onClicked: _asyncToGenerator(addnames)()
}

A generator function with yield on Promises is currently the closest thing we have to mimic the async/await pattern. You can use a transpiler like Babel https://babeljs.io/ with the babel-plugin-transform-async-to-generator to convert your async/await pattern to generator function/yield pattern.

Here's a table to help you do simple transpilations of async/await pattern to generator function/yield pattern:

async/await pattern generator function/yield pattern
async function() { ... } function*() { ... }
await new Promise(...) yield new Promise(...)
await async function() { ... } yield* function*() { ... }

Then wrap the top-most async function() inside an _asyncToGenerator() call.

To demonstrate both Promise-chaining and generator functions with yield, I've created a Page replacement called AsyncPage which includes both _asyncToGenerator() and setTimeout() functions.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
AsyncPage {
    id: asyncPage
    property bool busy: false
    header: Frame { Label { text: qsTr("Names (count:%1)").arg(listModel.count) } }
    ListView {
        anchors.fill: parent
        model: ListModel { id: listModel }
        delegate: Frame { Label { text: name } }
        BusyIndicator { anchors.centerIn: parent; visible: busy }
    }
    footer: Frame {
        RowLayout {
            Button {
                text: qsTr("Add Names")
                enabled: !busy
                onClicked: Promise.resolve()
                    .then( () => busy = true )
                    .then( () => listModel.clear() )
                    .then( () => listModel.append( { name: "Bill Gates" } ) )
                    .then( () => new Promise(resolve => setTimeout(resolve, 1000) ) )
                    .then( () => listModel.append( { name: "Steve Jobs" } ) )
                    .then( () => new Promise(resolve => setTimeout(resolve, 1000) ) )
                    .then( () => listModel.append( { name: "Jeff Bezos" } ) )
                    .then( () => busy = false )
            }
            Button {
                text: qsTr("Add Names")
                enabled: !busy
                property var sleep: function*(delay) {
                    yield new Promise(resolve => setTimeout(resolve, delay));
                }
                property var addname: function*(name) {
                    yield listModel.append( { name: name } );
                }
                property var addnames: function*() {
                    busy = true;
                    listModel.clear();
                    yield* addname("Bill Gates");
                    yield* sleep(1000);
                    yield* addname("Steve Jobs");
                    yield* sleep(1000);
                    yield* addname("Jeff Bezos");
                    busy = false;
                }
                onClicked: _asyncToGenerator(addnames)()
            }
        }
    }
}

// AsyncPage.qml
import QtQuick
import QtQuick.Controls
Page {
    function _asyncToGenerator(fn) {
        return function() {
            var self = this,
            args = arguments
            return new Promise(function(resolve, reject) {
                var gen = fn.apply(self, args)
                function _next(value) {
                    _asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value)
                }
                function _throw(err) {
                    _asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err)
                }
                _next(undefined)
            })
        }
    }
    
    function _asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
        try {
            var info = gen[key](arg)
            var value = info.value
        } catch (error) {
            reject(error)
            return
        }
        if (info.done) {
            resolve(value)
        } else {
            Promise.resolve(value).then(_next, _throw)
        }
    }
    
    function setTimeout(func, interval, ...params) {
        return setTimeoutComponent.createObject(asyncPage, { func, interval, params, running: true } );
    }
    
    Component {
        id: setTimeoutComponent
        Timer {
            property var func
            property var params
            onTriggered: {
                func(...params);
                destroy();
            }
        }
    }
}
like image 143
Stephen Quan Avatar answered Jul 28 '26 15:07

Stephen Quan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!