javascriptasynchronousgjsmainloop

Asynchronous Soup calls


I'm working on simple extension for Gnome DE and have some trouble wrapping my head around asynchronous Soup calls and the event loop.

Here's what I have:

_httpSession = new Soup.Session();

let token = 'sometoken'
let url = 'someurl';
let _allData = [];
let elements = [1,2];

for (let el of elements) {
    let message = Soup.form_request_new_from_hash('GET', url + el, { access_token: token });
    _httpSession.queue_message(message, () => {
        if (message.status_code != Soup.KnownStatusCode.OK) {
            _error(message.status_code.toString());
        }
        try {
            message = JSON.parse(message.response_body.data).items;
        } catch (e) {
            _error(e.toString());
        }
        _allData = _allData.concat([el, message]);
    });
}

Given the asynchronous calls in a for loop above, how to make sure that _allData.concat() has been executed for all iterations? I want to print out the _allData variable, but only when concatenations for each el were executed.


Solution

  • Easiest way is probably an async-await pattern:

    // Your request function can be turned into a Promise:
    function requestFunc(session, message) {
        return new Promise((resolve, reject) => {
            session.queue_message(message, () => {
                try {
                    if (message.status_code === Soup.KnownStatusCode.OK) {
                        let result = JSON.parse(message.response_body.data);
    
                        resolve(result);
                    } else {
                        reject(new Error(message.status_code.toString()));
                    }
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
    
    // Then you can await each in a loop
    async function logRequestResults(session, url, token, elements) {
        try {
            let results = [];
    
            for (let el of elements) {
                let message = Soup.form_request_new_from_hash('GET', url + el, {
                    access_token: token
                });
    
                let result = await requestFunc(session, message);
    
                results = results.concat([el, results.items]);
            }
    
            // Success; all requests completed
            log(results);
        } catch (e) {
            // An error occurred somewhere in the loop
            logError(e);
        }
    }
    
    // Using the function
    _httpSession = new Soup.Session();
    
    let token = 'sometoken'
    let url = 'someurl';
    let elements = [1,2];
    
    logRequestResults(session, url, token, elements);
    

    Depending what you're actually doing with your results, you might want to refactor that. The important part is turning a func(something, () => {}) pattern into a Promise you can await in a loop.

    Also note, that async functions implicitly return a Promise, those those can be used with await as well.