Javascript FFI with callbacks with less code

ffi
#1

I’m trying to FFI https://github.com/Leonidas-from-XIV/node-xml2js#simple-as-pie-usage

The API is as follows:

    parser.parseString(data, function (err, result) {
        console.dir(result);
        console.log('Done');
    });

When expanding this using https://blog.drewolson.org/purescript-async-ffi as a reference, I get

exports._parseString = function (parser) {
  return function (data) {
    return function (onError, onSuccess) {
      parser.parseString(data, function (err, data) {
        if (err) {
          onError(err);
          return;
        }

        onSuccess(data);
      });

      return function (cancelError, onCancelerError, onCancelerSuccess) {
        onCancelerSuccess();
      };
    };
  };
};

And

parseString :: Parser -> String -> Aff Foreign
parseString p s = fromEffectFnAff $ _parseString p s

on the PureScript side.

Is there a way to achieve this with a whole lot less JS code, e.g.

exports._parseString = function(parser, input, cb) { parser.parseString(input, cb) }
#4

EDIT:

I’ve just checked your linked docs and there is xml2js promised based API xml2js.parseStringPromise. In the linked article there is a solution for simple promise based FFI so please ignore my all my hacky responses and use promise based API + aff-promise directly.

OLD:

Maybe a bit hacky way by using node promisify + aff-promise. I hope I typed this correctly this time:

const util = require('util');

exports.parseStringImpl = function(parser) {
  return function() {
    return util.promisify(function(s, c) { parser.parseString(s, c); });
  }
}

and

import Control.Promise as Promise

foreign import parseStringImpl :: Parser -> Effect (String -> Promise ...)

parseString parser s = liftEffect (parseStringImpl parser) >>= \p -> Promise.toAff (p s)

Not tested so it is possibly a rubbish :wink:

#5

Thanks for the aff-promise hint. Shortest JS code I could come up with:

exports._parseStringPromise =
  function(parser, data) { return parser.parseStringPromise(data) }
foreign import _parseStringPromise ::
  EffectFn2 Parser String (Promise Foreign)

parseString :: Parser -> String -> Aff Foreign
parseString p s = toAffE $ runEffectFn2 _parseStringPromise p s
1 Like
#6

You can use arrow function to get rid of EffectFn2 and runEffectFn2. Not tested

exports._parseStringPromise =
  parser => data => () => parser.parseStringPromise(data)
foreign import _parseStringPromise ::
  Parser -> String -> Effect (Promise Foreign)

parseString :: Parser -> String -> Aff Foreign
parseString p s = toAffE $ parseStringPromise p s