Async and threads

Background

JavaScript is generally speaking single threaded (there are web workers and other such inventions, but these are generally considered separate processes, that share no address space with the main process). If a function needs to wait for some input (e.g., web request), you need to supply a callback function that gets executed when the data is available.

downloadData("https://example.com/", (err, data) => {
    if (err) { ... }
    else {
        let parsed = JSON.parse(data)
        ...
    }
})

This becomes somewhat problematic when you start to nest these. A simple web search on “callback hell” will tell why.

One way to fix this is to use promises, but the idea remains the same — in the .then() handler you provide the function to execute when data is available, but the advantage is that you can often avoid nesting them (functions returning promises are often by convention called somethingAsync):

downloadDataAsync("https://example.com/")
    .then(data => {
        let parsed = JSON.parse(data)
        ...
    }, err => { ... })
    .then(() => downloadDataAsync("https://somewhere-else.com/"))
    .then(somewhere => ...)

There are proposals of introducing C#-style async/await to JavaScript. In fact TypeScript can compile async/await to ES6 generators (yield). In that case you can use await operator to make a call to a promise-returning function look sequential:

let parsed = JSON.parse(await downloadDataAsync("https://example.com/"))
...
let somewhere = await downloadDataAsync("https://somewhere-else.com/")
...

Needless to say, this is way more readable and easier to get right than the previous two solutions. In fact, it lets you simulate cooperative multithreading — you think you have multiple threads, but only one of them runs at any given time, and you can be sure your thread will not get interrupted until the point where it uses await.

Promise? Await? And what is that for loop thing again?

Now, all of this is great, but not really something you want to explain to someone who’s just trying to learn what a for loop is.

For this reason, PXT lets users call async functions, as if they were regular functions. This loses information about where your thread can be interrupted, but we can hopefully recover that in the IDE (by for example displaying a little clock next to async calls).

let parsed = JSON.parse(downloadData("https://example.com/"))
...
let somewhere = downloadData("https://somewhere-else.com/")
...

Supporting async functions this way is one of the main reasons why we have our own compilation scheme from TypeScript to JavaScript (cross-browser debugger is another major one).

Implementing async functions

Currently, to implement an async function, you first need to add //% promise attribute to the declaration:

//? Downloads data from remote site.
//% promise shim=basic::downloadData
export function downloadData(url:string) { return "" }

In the simulator you return a promise:

export function downloadData(url:string) {
    return new Promise<string>((resolve, reject) =>
        $.get(url, (data, status) => {
            resolve(data)
        }))
}

It is also possible to use //% async and use getResume() function to get a callback. You can see some older code do that.

Note, that you can generate TypeScript definition from the simulator files, which will take care of the //% promise and //% shim=... annotations.