Dismiss
  • Toggle Theme
  • View as Mobile

Forcing async functions to run sync in node.js

Handy snippet for forcing an asynchronous promised based library/function to run synchronously.

This works by spawning a child_process using spawnSync.

The sync-rpc module, will handle this for us in node.

Note: You shouldn't use this for production server side code. Forcing sync functions will block the event loop and can result in much slower programs. In some cases, like in a CLI or in a simple script you are writing, this doesn't matter much.

Sometimes, you just need things to work without an entire refactor of a script/library to get it working. This is a quick and easy way to force async to sync.

1. Install the module

npm install sync-rpc

2. Put your async code into a new file.

// async-thing.js file
function asyncFunction() {
  return (paramOne, paramTwo) => {
    // Your async code here
    return delay(2000).then(() => {
      return {
        one: `${paramOne}-value`,
        two: `${paramTwo}-value`,
      }
    })
  }
}

// Simulate a async promise delay
function delay(t, v) {
  return new Promise((resolve) => {
    setTimeout(resolve.bind(null, v), t)
  })
}

module.exports = asyncFunction

3. Import your async code and wrap it.

Import the ./async-thing file and wrap with sync-rpc module.

This will pause execution and make the async code block and run sync.

const forceSync = require('sync-rpc')
const syncFunction = forceSync(require.resolve('./async-thing'))

// inside your thing that needs to be sync (for whatever reason)
const paramOne = 'foo'
const paramTwo = 'bar'
console.log('start')
const syncReturn = syncFunction(paramOne, paramTwo)
console.log('syncReturn', syncReturn)
// result after 2 seconds
// { one: `foo-value`, two: `bar-value` }

// Do the rest of your stuff with `syncReturn` value

Live example

https://repl.it/@DavidWells1/SecondaryUnnaturalArguments

Alt approach using worker threads

This is an interesting approach using worker threads described by https://samthor.au/2021/block-nodejs-main-thread

// main.js
import { Worker, MessageChannel, receiveMessageOnPort } from 'worker_threads'
const { 
  port1: localPort, 
  port2: workerPort 
} = new MessageChannel()

const shared = new SharedArrayBuffer(4)

// send the shared buffer and port to the Worker
const workerData = { 
  shared, 
  port: workerPort 
}
const w = new Worker('./task.js', { 
  workerData, 
  transferList: [workerPort] 
})

const int32 = new Int32Array(shared)
Atomics.wait(int32, 0, 0)  // blocks until notified at int32[0]

const message = receiveMessageOnPort(localPort)
console.warn('got fetch data', message)

Tasks.js

import { workerData } from 'worker_threads'
import fetch from 'node-fetch'

const { 
  shared, 
  port 
} = workerData

console.warn('fetching')
const text = await fetch('https://google.com').then((r) => r.text())
console.info('fetched a blog')

port.postMessage(text)

const int32 = new Int32Array(shared)
Atomics.notify(int32, 0)