Hello

arrays are

lists of data

const items = ["apple", "orange", "strawberry"];

for (let i = 0; i < items.length; i++) {
    console.log(items[i]);
}

arrays are like pure functions, where the same input gives the same output*

*unless you're changing the array between look-ups

we can simulate the

way arrays behave

const Iterator = function () {
    let i = 0;

    const items = ["apple", "orange", "strawberry"];

    const done = function () {
        return i >= items.length;
    };

    return {
        done,
        next: function () {
            i++;
            return items[i - 1];
        }
    };
};

const iterator = Iterator();

while (!iterator.done()) {
    console.log(iterator.next());
}

Why is this useful?

to save resources in

very large lists

we can pause and

resume iteration

because we don't know all the items in the beginning

our programs are arrays

 

each line or function is indexed by line and column

...this is how we even

have stack traces

could we think about our functions in the same way we think about iterators?

generators!

function* (items) {
    for (let i = 0; i < items.length; i++) {
        yield items[i];
    }
}
const Generator = function () {
    const items = ["apple", "orange", "strawberry"];

    return {
        [Symbol.iterator]: function* () {
            for (let i = 0; i < items.length; i++) {
                yield items[i];
            }
        }
    };
};

const generator = Generator();

for (const next of generator) {
    console.log(next);
}

so...let's generalise

class Task {
    constructor(generator) {
        this.generator = generator();
    }

    tick() {
        return this.generator.next();
    }
}
class Runner {
    tasks = [];

    add(task) {
        this.tasks.push(task);
    }

    tick() {
        const task = this.tasks.shift();

        if (!task) {
            return false;
        }

        if (!task.tick().done) {
            this.add(task);
        }

        return this.tasks.length;
    }
}
const runner = new Runner();

runner.add(
    new Task(function* () {
        for (let i = 0; i < 5; i++) {
            console.log(`first task is ${i}`);
            yield;
        }
    })
);

runner.add(
    new Task(function* () {
        for (let i = 0; i < 3; i++) {
            console.log(`second task is ${i}`);
            yield;
        }
    })
);

while (runner.tick());
[Log] first task is 0
[Log] second task is 0
[Log] first task is 1
[Log] second task is 1
[Log] first task is 2
[Log] second task is 2
[Log] first task is 3
[Log] first task is 4

codepen.io/assertchris/pen/oNwzzoB

let's make this a bit

easier to use...

const ayyysync = function (generatorFactory) {
    return generatorFactory();
};

const ayyywait = function (generatorInstance) {
    let previous = undefined;

    while (true) {
        const next = generatorInstance.next();

        if (!next.done) {
            previous = next.value;
        } else {
            break;
        }
    }

    return previous;
};
const task = function () {
    return ayyysync(function* () {
        for (let i = 0; i < 5; i++) {
            console.log(`third task is ${i}`);
            yield "done";
        }
    });
};

const result = ayyywait(task());

does this look familiar?

const task = function () {
    return ayyysync(function* () {
        // ...
    });
};

const out = ayyywait(task());

vs.

const task = async function() {
	// ...
};

const out = await task();

could it be that simple?

not exactly...

all the awaitable things need

to run in parallel

const [_, changeSets] = await Promise.all([
  clearCache(),
  getChangeSets({ state: published }),
])

await cacheChangeSets(changeSets)
const sleep = function (ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(`slept for ${ms}`);
        }, ms);
    });
};

const task = async function () {
    console.log("starting...");

    const result = await sleep(100);
    console.log(`resulted in ${result}`);

    return sleep(200);
};
const task = Awaitable([
    function (__previous, __context) {
        console.log("starting...");
    },
    sleep(100),
    function (__previous, __context) {
        __context["result"] = __previous;
        console.log(`resulted in ${__context["result"]}`);
    },
    sleep(200)
]);
// in the background...

let tasks = [];

setInterval(function () {
    let remaining = [];

    for (const task in tasks) {
        if (task.awaiting && task.awaiting.isResolved) {
            task.resume(task.awaiting.value);
        }

        if (!task.isResolved) {
            remaining.push(task);
        }
    }

    tasks = remaining;
}, 0);

this ends up being a lot* of code

*event loop, interrupts, network, fs, threading, promises

github.com/libuv/libuv

used by node.js + php async implementations

speaking of...

php has multiple*

simulations of async + await

*reactphp, amphp

use Psr\Http\Message\ServerRequestInterface;
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;

$http = new HttpServer(function(ServerRequestInterface $request) {
    return new Response(
    	200,
        ["Content-Type" => "text/plain"],
        "Hello world\n",
    );
});

$socket = new SocketServer("127.0.0.1:8080");
$http->listen($socket);
use Amp\Http\Server\HttpServer;
use Amp\Http\Server\RequestHandler\CallableRequestHandler;
use Amp\Http\Server\Response;
use Amp\Http\Status;
use Amp\Loop;
use Amp\Socket\Server;

Loop::run(function() {
    $server = new HttpServer(
      [Server::listen("127.0.0.1:8080")],
      new CallableRequestHandler(function() {
          return new Response(
              Status::OK,
              ["content-type" => "text/plain"],
              "Hello world\n",
          );
      })
    );

    yield $server->start();
});

php doesn't have

async + await keywords

github.com/preprocess/pre-async

$task = async function($client) {
    $this->prepare($client);
    await $update($client);
};
$task = [
    ($update = $update ?? null),
    "fn" => function ($client) use (&$update): \Amp\Promise {
        return \Amp\call(function () use (&$update) {
            $this->prepare($client);
            yield $update($client);
        });
    }
]["fn"];

In summary...

  1. generators can be paused and resumed

  2. any language that has this functionality can simulate async + await

  3. you're better off using something like libuv to do the grunt work

  4. language macros will help to get the ideal syntax

questions?

twitter.com/assertchris

slides.com/assertchris/inventing-async-await-september-2021

Inventing Async + Await (September 2021)

By Christopher Pitt

Inventing Async + Await (September 2021)

  • 49
Loading comments...

More from Christopher Pitt