0

I'm aware of the power of promises, however I have several old functions that are synchronous:

function getSomething() {
  return someExternalLibrary.functionReturnsAValue()
}

console.log(getSomething()); // eg prints 'foo'

Unfortunately, when someExternalLibrary updated, it has removed functionReturnsAValue() and has lumped me with functionReturnsAPromise():

function getSomething() {
  return someExternalLibrary.functionReturnsAPromise()
}

console.log(getSomething());  // now prints '[object]'

This of course, breaks absolutely everything written that depends on what used to be a simple value.

Obviously, I'd prefer two things:

  1. ask the original library to keep a synchronous return value. (Not going to happen -- b/c they have refused)
  2. A way to actually wait for a value

I have read numerous articles on why promises are great, ad nauseam, but the simple fact is: If I embrace promises, all I really do is shuffle promises onto some other part of the code, which then must deal with the promise of a value...

Is there a way (in nodejs) to actually wait for a promise to get itself together?

The best I can find is to use coroutines and yield, but really, it's still passing the buck. To be clear, I want the function getSomething to continue to return a value. Is there a way to do it?

Clearly, I fear I've misunderstood something about Promises...

The app is for non-browser implementations and runs purely from the command line. I've been trying to understand how bluebird's reflect() might help, to no avail.

(Yes, I'm aware this question has been asked many times in various formats, but I can't find a suitable answer to the core issue. If anything, I'm looking for the opposite of this question. The closest related (but unhelpful) question I can find is: Managing promise dependencies.)

Community
  • 1
  • 1
cmroanirgo
  • 7,297
  • 4
  • 32
  • 38
  • 2
    regarding "ask the original library to keep a synchronous return value. (Not going to happen -- b/c they have refused)" - I don't understand why you can't pin to a version of the library that didn't have the promise update. That's the canonical answer to your problem anyway. You can't do what you're asking to do with promises. – aaaaaa Nov 25 '16 at 05:29
  • @aaaaaa: because in the process of fixing a particular fault, they also gave me a promise ;). So, I'm lumped with an old crashing library, or a new promisi-fied one. – cmroanirgo Nov 25 '16 at 05:32
  • Pinning the version would be good quick fix. Finding a way to block/wait would be a not so quick, but also bad fix. You have to update your code to work with promises if you want to continue to use the library ecosystem (and as you already found out, bug fixes will probably not be back-ported to the old stuff). – Thilo Nov 25 '16 at 05:33
  • 3
    Then yeah, you either refactor your code to allow for promises (they are confusing, don't think it will be a lesson learned overnight) - or you pin to the old version and fix the "broken" issue sans promises. – aaaaaa Nov 25 '16 at 05:34
  • @aaaaaa: I, of course, will wait for any other answer than that one ;) (Or in other words -- I hate being roped into fixing someone else's crappy code). I was hoping I'd missed some glaring sync<=>async bridge between promises and not. – cmroanirgo Nov 25 '16 at 05:38
  • `I hate being roped into fixing someone else's crappy code` - think how much extra you'd have to know to tinker with your little program without other peoples `crappy code` – Jaromanda X Nov 25 '16 at 05:43
  • @JaromandaX: I'm aware you're remonstrating me for accusing others. Fair enough. I have written this stuff in other languages, multiple times, but the truth is, I'd keep using this other person's code b/c they're maintaining it. If I fork and fix it, that's something, but I have no interest in *maintaining* the code afterwards. – cmroanirgo Nov 25 '16 at 05:51
  • 1
    If the library author change it to an async function, it must be done with reasons. It's usually because there are events or i/o involved and which cannot be done without a callback(or promise). Since Node.js is using the non-blocking I/O model and event driven, if you change those to synchronous process, then it will greatly reduce the performance of Node.js and it will not be Node.js. – iKoala Nov 25 '16 at 06:07
  • Heads up: I've found a mutex library, which is something that can be used: https://github.com/ben-ng/mutex-js. The downside is that it requires a redis server running :( – cmroanirgo Nov 25 '16 at 19:26

2 Answers2

0

There's the concept of generator functions. These are a special kind of function in both syntax (asterisk notation) and semantics. Unlike regular functions, generator functions return something that's also new to ECMAScript: iterators. Iterators happen to be objects made specifically to be iterated on, e.g. with the all new for...of loop. They can be also iterated on manually by calling their 'next' method. Each such call produces an object containing two properties: 'value' (iterator's current value) and 'done' (a boolean indicating whether we reached the last value of the iterable). However, the best thing about generator functions is their ability to suspend their execution each time a keyword 'yield' is encountered. Let's have a glimpse of how it all works together:

'use strict';

let asyncTask = () =>
  new Promise((resolve, reject) => {
    if (Math.random() > 0.5) {
      resolve(1);
    } else {
      reject(new Error('Something went wrong'));
    }
  });

let makeMeLookSync = fn => {
  let iterator = fn();
  let loop = result => {
    !result.done && result.value.then(
      res => loop(iterator.next(res)),
      err => loop(iterator.throw(err))
    );
  };

  loop(iterator.next());
};

makeMeLookSync(function* () {
  try {
    let result = yield asyncTask();

    console.log(result);
  } catch (err) {
    console.log(err.message);
  }
});
mohan rathour
  • 420
  • 2
  • 12
  • Is this not the coroutine and yield I mentioned in my question? You have shown a lot of code here, but it does not seem address the answer of keeping ``getSomething`` actually synchronous. – cmroanirgo Nov 25 '16 at 05:45
  • 4
    you can not change something that is asynchronous to be synchronous ... full stop, stop looking :p – Jaromanda X Nov 25 '16 at 05:47
  • @JaromandaX. There's the rub. I see a whole lot of push toward async code, which I have no particular problem with, but many people are forcing code to be async, for really trivial purposes, when it suits little, if any purpose. I was hoping there was something to hold back the tide of the rush to promisify everything. – cmroanirgo Nov 25 '16 at 05:57
  • 1
    It's just an example of making async to sync call via using generators – mohan rathour Nov 25 '16 at 06:04
0

The short answer

I am told repeatedly: You can't undo functions that have been promisified.

Edit: An upcoming solution

It appears that the ES2017 (although still draft), goes a long way in making promisified code easier to work with: https://ponyfoo.com/articles/understanding-javascript-async-await

It seems that there is also a node library ready for this support too: https://github.com/normalize/mz.

Using this methodology, having apis converted to Promises won't be so bad (although it still appears that promises still poison the rest of the codebase):

const fs = require('mz/fs')

async function doSomething () {
  if (await fs.exists(__filename)) // do something
}

The rest of this answer is just a general commentary on the problem.

Why we need a solution

Let's start with a sample piece of traditional synchronous code, in 3 flavours from more 'older-fashioned' to 'newer':

This is the traditional javascript way, requiring exception based programming to handle unexpected errors:

function getSomething() {
    if (someproblem) throw new Error('There is a problem');
    return 'foo';
}

However, adding try/ catch statements becomes very laborious and tedious, very quickly.

With the advent of node.js, callbacks were made popular, which nicely circumvented the issue, since each caller was explicitly forced to deal with error conditions in the same callback. This meant less errors in the caller's code:

function getSomething(callback) {
    if (callback) {
        if (someproblem) 
            callback(new Error('There is a problem'), null);
        else 
            callback(null, 'foo');
    }
    return 'foo';
}

Then, the after some teething issues, node.js quickly proved itself for server-side communications, and people were amazed at the speed that asynchronous solutions provided. Node application frameworks like Express and Meteor grew, which focused on this.

Unfortunately, using the same callback scheme quickly became troublesome and the developers dealing in asynchronous code started using Promises in an effort to linearize the code, to make it readable, like the traditional (try/catch) code was.

The problem is that it got evangenlized too much. Everyone started thinking that Promises are the way to go. Personally, I call it a poison on a codebase. Once you have anything that uses Promises, your whole codebase must become asynchronous. This is not always a sensible nor a practical solution, IMHO.

The worst of all side effects is that the above function, even though it is completely synchronous, can be written in Promises too:

var bluebird = require('bluebird');

function getSomething() { 
    // IMHO, this is ridiculous code, but is increasingly popular.
    if (someproblem) return Promise.reject(new Error('There is a problem'));
    return Promise.resolve('foo');
}

For those who doubt this is a problem, perhaps should look at the SO question: How do I convert an existing callback API to promises?. Pay particular attention to #3, Node-style callback.

So, for anyone who cares, I would like to suggest that there needs to be a 'pill' for Promises. I urge that we need more than promises: we need results, and sometimes in a timely manner.

Take a look at the default node.js api. It does not use Promises. It also provides both synchronous and asynchronous calls to appropriate parts of the api (eg File System).

For those of you who feel tempted to downvote this answer: that is your prerogative, but there are clear issues on when Promises are not the answer, and I feel strongly that there are cases when we need to be able to re-synchronize decoupled code.

I also apologize for this 'blog-post' styled answer.

Community
  • 1
  • 1
cmroanirgo
  • 7,297
  • 4
  • 32
  • 38
  • 1
    Your problem is not "being unable to convert a promise to synchronous code". Your problem is depending on broken code whose fix introduced an api change. This answer is not relevant to the problem in the question. – aaaaaa Nov 26 '16 at 20:06
  • @aaaaaa. Although not explicitly mentioned, I have several projects that have been 'promisified' underneath. This is not a specific problem related to just one particular api... and so the question and the answer are valid. What I'd love are mutexes or equivalent, then I can at least *wait* (in a thread safe manner) for a promise to resolve (or fail) – cmroanirgo Nov 27 '16 at 05:46