npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

babel-plugin-use-async

v0.0.2

Published

Implements 'use async' ECMAScript proposal

Downloads

4

Readme

babel-plugin-use-async

Implements the "use async" ECMAScript proposal.

Not intended for production use!

Why "use async"?

async and await are great but in practice the vast majority of async function use await and they are subject to the What Color is Your Function critique.

async and await were designed to minimize their effect on existing code and specifications. While that is an entirely reasonable design goal it creates the problem of not knowing whether a call to an async function without an await is intentional or accidental and the annoyance of having to litter your code with await statements.

"use async" is an option, modeled on "use strict", that makes the following changes to the language:

  • in async functions:

    • calls to async functions are await'd by default
    • the async keyword can be used before an async function call to indicate that it should not be await'd
    • await works as before
  • in plain functions:

    • calls to async functions without the async keyword before them will throw an error
    • calls to regular functions with the async keyword will throw an error
    • await is invalid as before

The end result is a launguage where (almost) all code follows a traditional sequential control flow and the ambiguity about whether or not a function call should be await'd is eliminated.

Requiring async to be used when calling async functions from plain functions prevents a similar ambiguity from arising with regards to whether or not a function was intended to be async.

This proposal aims to resolve the issues raised by Proposal to add keyword "nowait" and async/await -> await/async: a simpler, less error-prone async syntax with the least amount of breakage and greatest amount of conceptual continuity.

Examples

Simple

async function getValueAsync() {
    // get value from regular function that returns promise
    const res = await http.get("/foo")
    // return value
    return res.val
}

function addOneSync(val) {
    return val + 1
}

// seamlessly combine sync and async functions in an async function
async function bam() {
    let y = getValueAsync() // 1
    y = addOneSync(y) // 2
}

// use async functions from a regular function
function baz() {
    let y = async getValueAsync() // <Promise>
    y.then((y) => {
        y = addOneSync(y) // 2
    })
}

// calling async function without async is an unambiguous error
function bif() {
    let y = getValueAsync() // Error!
}

This example demostrates how "use async" eliminates the ambiguity with await by requiring the async keyword to be used when calling async functions from regular functions.

Missing async on function declaration

// SyntaxError on await
function getValueAsync() {
    // get value from regular function that returns promise
    const res = await http.get("/foo")
    // return value
    return res.val
}

// Error calling async function without async
function bam () {
    let y = getValueAsync() // 1
    y = addOneSync(y) // 2
}

With "use async" mistakenly failing to declare a function as async will result in an error if that function either attempts to await or attempts to call another async function.

Missing async on function call

// starts this, that and otherThing at same time, waits for results
async function collectResults() {
    return Promise.all([
        async this(),
        async that(),
        async otherThing()
    ])
}

// runs this, that and otherThing sequentially, then returns results
async function collectResults() {
    return Promise.all([
        this(),
        that(),
        otherThing()
    ])
}

Mistakenly omitting the async keyword may result in incorrect execution but will usually return correct results.

This is very different than mistakenly omitting an await with standard JavaScript, which will almost always yield incorrect results, sometimes in obscure and unpredictable ways.

Implementation

Async function call from async function

async function foo() {
    let x = bar()
}

Transpiles to

const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor

async function foo() {
    let x = (bar instanceof AsyncFunction ? await bar() : bar())
}

This does not work for code that does not use real async functions and it is not intended to.

The goal here is to reduce ambiguity about how code will behave at runtime so regular functions that return Promises should not be implicitly await'd.

Benchmarks

$ node benchmark/async-function.js 
Sync Function Calling Sync Function x 420,293,162 ops/sec ±0.07% (93 runs sampled)
Async Function Calling Sync Function x 105,306,976 ops/sec ±0.84% (92 runs sampled)
Await on Sync Function x 2,065,160 ops/sec ±5.59% (46 runs sampled)
Async Function with Explicit await x 2,114,176 ops/sec ±4.58% (51 runs sampled)
Async Function with Implicit await x 2,171,542 ops/sec ±3.06% (81 runs sampled)

The instanceof call is so insignificant compared to the cost of await that it falls entirely within the range of normal test variation.

Regular function call from regular function

function foo() {
    let x = bar()
}

Transpiles to

const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
class AsyncError extends Error {
    constructor(message) {
        super(message || "async function called without async keyword")
    }
}

function throwOnAsync(func) {
    if (func instanceof AsyncFunction) throw new AsyncError()
    return true
}

function foo() {
    let x = (throwOnAsync(bar) && bar())
}

Benchmarks

$ node benchmark/sync-function.js 
Sync Function Calling Sync Function x 420,586,378 ops/sec ±0.07% (94 runs sampled)
Sync Function Calling Sync Function with Async Test x 66,005,927 ops/sec ±0.39% (85 runs sampled)
Sync Function Calling Sync Function with Async Test Node x 32,895,049 ops/sec ±0.57% (91 runs sampled)
Sync Function Calling Sync Function with Inline Async Test x 66,616,830 ops/sec ±0.53% (88 runs sampled)
Sync Function Calling Sync Function with Inline Async Test Node x 33,452,216 ops/sec ±0.21% (94 runs sampled)
Sync Function Calling Sync Function x 10000 x 107,901 ops/sec ±0.27% (95 runs sampled)
Sync Function Calling Sync Function with Async Test x 10000 x 23,968 ops/sec ±0.22% (97 runs sampled)
Sync Function Calling Sync Function x 10000 with 1 Async Test x 107,115 ops/sec ±0.22% (96 runs sampled)

Compared to a plain function call the instanceof test does have a cost of 5-10X which is pretty expensive, especially if it is in a tight loop.

For transpiling this can be addressed by hoisting the test outside of loops where possible but this does not fix the case where a synchronous function with "use async" enabled is called in tight loop by another function.

Performance sensitive synchronous code that is likely to be run repeatedly should avoid "use async" until this can be resolved.