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

node-force-sync

v0.1.3

Published

Forces an async function to run synchronously using a separate node process

Downloads

351

Readme

node-force-sync

npm version github actions Node Versions Minified Size Conventional Commits

WARNING: This package should only be used in the exceptionally rare situation that converting to async code is not an option. Please don't use it as an excuse not to learn how Promises and async functions work.

Overview

node-force-sync allows you to force synchronous execution of an async function. This is not the same thing as the await keyword, which is really just async execution made to look like sync execution. This package actually runs your async functions synchronously.

Limitations

Node.js itself provides no mechanism for forcing async code to block. Thus, the solution is hacky and has significant limitations:

  • only works in Node.js (not in the browser)
  • the async function can't rely on any surrounding scope
  • all arguments and return values must be JSON-serializable
  • error handling is limited to error messages (no stack traces)
  • console.log calls will not be visible
  • very inefficient due to use of file system and parallel Node.js processes
  • transpilation (TypeScript, Babel) may break your function (but workarounds are available)

Usage

const { forceSync } = require('node-force-sync');

const myAsyncFunc = async (...args) => { /* ... */ };
		
const mySyncFunc = forceSync(myAsyncFunc);

mySyncFunc(1, 2, 3);

Installation

$ npm install node-force-sync

or

$ yarn add node-force-sync

API

forceSync(asyncFunc, config?)

Signature

function forceSync<A extends any[], R extends any>(asyncFunc: (...args: A) => Promise<R>, config?: Partial<ForceSyncConfig>): (...args: A) => R;

Description

Accepts an async function (and optional config) and returns a synchronous version of that function. Due to implementation details, your function is subject to several limitations:

  • Your function must return a Promise. If it's an async function, this requirement is already met.
  • Your function arguments and return values must be JSON-serializable.
  • Your function cannot rely on any surrounding scope, even imports. Your function may still import node modules, but it must do so via require() from within the function itself.
  • If you use a transpilation step (e.g., TypeScript or Babel), the above bullet may be inadvertently violated. For example, TypeScript targeting pre-es2017 will generate an __awaiter function out of scope. See Transpilation Workarounds for more details on how to deal with this.

forceSync(asyncFuncStr, config?)

Signature

function forceSync<A extends any[], R extends any>(funcStr: string, config?: Partial<ForceSyncConfig>): (...args: A) => R;

Description

Identical to forceSync(asyncFunc, config?), except you pass a function string instead of a function. There are benefits and drawbacks to using this overload:

  • You don't have to worry about transpilation breaking your code because your function string is executed as-is.
  • On the otherhand, you have to worry about what you lose by skipping transpilation, such as type-safety or new JS syntax.
  • Typing code as a string is prone to errors, unless your editor is fancy enough to recognize the embedded language.

In general, you should really only need this overload if you're trying to get around the issues caused by transpilation.

ForceSyncConfig

Both overloads of forceSync allow an optional config argument.

Shape

type ForceSyncConfig = {
	tagOpenWrappers?: [string, string],
	tagCloseWrappers?: [string, string],
	tmpFilePath?: string,
	nodeExecutable?: string,
	debug?: boolean,
};

Defaults

const forceConfigDefaults: ForceSyncConfig = {
	tagOpenWrappers: ['!!!', '!!!'],
	tagCloseWrappers: ['!!!/', '!!!'],
	tmpFilePath: '.',
	nodeExecutable: 'node',
	debug: false,
};

Details

  • tagOpenWrappers / tagCloseWrappers - In order to distinguish your function output/errors from other console output, the output/errors have to be wrapped in tags, for example: !!!OUTPUT!!!"your function output"!!!/OUTPUT!!!. These tags have a beginning, middle, and end sections. In this example, the OUTPUT is the "middle", and !!! and /!!! are the "wrappers." If your function happens to be outputting text that would get confused with these tags, you can change the defaults. For example, you might wish the tags to look like this instead: <OUTPUT></OUTPUT>.
  • tmpFilePath - Your async code is written to a temporary JS file during execution. This is the path where you'd like these temporary files to be created.
  • nodeExecutable - Node.js must be invoked from the commandline as part of executing your async code.
  • debug - Logs extra information when your code is run, such as
    • the generated code being executed
    • the temporary filename
    • the raw function output/errors
    • the output/errors that were able to be parsed.

More Examples

Synchronous HTTP Request

const { forceSync } = require('node-force-sync');

const asyncGet = async (url: string) => {
	const axios = require('axios');
	return (await axios.get(url)).data;
};

const syncGet = forceSync(asyncGet);

const response = syncGet('https://postman-echo.com/get?foo=bar');

Transpilation Workarounds

If you use TypeScript, Babel, or some other transpilation tool, chances are your function will fail to execute properly when forced to run synchronously. The primary reason for this is code being generated out of scope of your function, which conflicts with one of the package's major limitations. There are a few ways to work around this issue:

Adjust transpilation settings

As long as your transpiler doesn't generate out-of-scope code, your function should run just fine. In the case of TypeScript, you need to target es2017 or later to avoid the generation of __awaiter and __generator. This is the best option if your runtime requirements allow it.

Use string version of forceSync

forceSync can also accept a function string rather than a function. The string you pass is not transpiled, so you can avoid the transpilation problem all together this way. This is a decent option if your code is relatively simple and you don't want to mess with transpilation settings.

Return Promise instead of using async function

In the case of TypeScript, __awaiter and __generator are only generated when async function is used. If you make your function manually return a Promise instead, TS won't generate the out-of-scope code. This is a good option if you can't target es2017 or later and you're unwilling to lose type-safety/intellisense by encoding your function as a string.

How It Works

Node.js itself cannot resolve a Promise synchronously. However, it can run a child process synchronously via execSync(). A Node.js process will also not exit until any pending Promises are resolved. So putting these two pieces together, you can "force synchronous execution" by running the async code in a child Node.js process, which won't exit until the Promise has resolved and the parent Node.js process can synchronously wait for.

If you want more details, you'll have to look at the source!