@piglovesyou/to-sync

synchronously execute asynchronous functions

Usage no npm install needed!

<script type="module">
  import piglovesyouToSync from 'https://cdn.skypack.dev/@piglovesyou/to-sync';
</script>

README

to-sync Node CI

A forked version of do-sync, also for babel-plugin-macros developers.

$ npm install @piglovesyou/to-sync
$ yarn add @piglovesyou/to-sync
Features
  • Enable lexical scope in a target async function for more flexible use
  • Typings for a generated sync function without generics parameters
import toSync from '@piglovesyou/to-sync'

export async function myAsyncFunc(a: string): Promise<string> {
  // ...
}

// Typed as `string => string` 😎
export const mySyncFunction = toSync(myAsyncFunc);
Caveats
  • Export your async function. Otherwise the child process cannot find it.
  • Call toSync next to your async function. If you can't, pass { filename } option to it.
  • This is slow naturally, as it launches a new process and communicate through serialized data. Try to avoid using it.

do-sync

do-sync is a small library that allows certian kinds of async functions to be executed synchronously in node.

Why Though? Isn't this a terrible idea?

babel-plugin-macros does not support asynchrony in macros1, but many vital libraries like sharp require asynchrony to function -- and, in fact do not support synchronous usage.

Example

import { doSync, AsyncFn, JSONObject } from 'do-sync';

interface resizeOpts extends JSONObject {
    width: number, height: number
}

interface resizeRet extends JSONObject {
    width: number, height: number, blob: string,
}

const resize = doSync(async (target: string, { width, height, ...jpegOpions }: resizeOpts): Promise<resizeRet> => {
    const sharp = require('sharp');
    const blob = 
        (await sharp(Buffer.from(target, 'base64'))
            .resize(width, height)
            .jpeg(jpegOpions)
            .toBuffer()).toString('base64');
    return { blob, width, height };
})

const myImage = resize('cool.png', {
    width: 10, height: 10
})

This package is used to implement image.macro, which dynamically resizes high-resolution images to multiple sizes at compilation time for use with webpack. It's a little rough around the edges, but works well and should provide some more concrete usage examples:

Limitations

  • The function will be completely extracted from its scope context. It is run as though it was on its own, in its own file.
  • Only JSON serializible parameters and response values are allowed.
  • Typescript will enforce JSON serializibility, but as a result pure Objects passed into functions, or returned by functions must extend or implement JSONObject.
  • If the process throws an error, properties that cannot be serialized to JSON will not cross the process boundary.

Footguns

ENOBUFS

do-sync uses a node subprocess and writes all code to STDIN. child_process.spawnSync has a default limit on STDIN input which can, if large JSON is transiting STDIN make your program explode. doSync takes an optional second parameter, opts, which has the same options as spawnSync -- the value is already very large (1GB), but you can set maxBuffer to something bigger if you encounter issues:

doSync(myFunc, {
    maxBuffer: 1024 * 1024 * 1024
})