2

I've been trying to use gm with Bluebird like this:

var gm = require('gm');
var bluebird = require('bluebird');
gm = bluebird.promisifyAll(gm);

But then, when I try something like this:

gm(req.file.buffer)
     .crop(tWidth, tHeight, tWidth/2, tHeight/2)
     .gravity('Center')
     .toBuffer()
  .then(...)

I get this error:

gm().toBuffer() expects a callback.

If I only promisify the buffer method:

bluebird.promisify(gm(req.file.buffer)
        .crop(tWidth, tHeight, tWidth/2, tHeight/2)
        .gravity('Center')
        .toBuffer)()
        .then(buff => {...})

I get this error:

TypeError: this.stream is not a function 6:36:21 AM web.1 | at toBuffer (/Users/danielrvt/IdeaProjects/twe-backend/node_modules/gm/lib/command.js:162:17)

If I don't use promises, it works just fine.

danielrvt
  • 10,177
  • 20
  • 80
  • 121
  • `promisifyAll` creates a `.toBufferAsync()` function that will return the promise. You'll need to call that instead of `.toBuffer()`. – Bergi Nov 28 '16 at 16:35
  • Your `promisify` approach doesn't work because the `toBuffer` function [isn't bound](http://stackoverflow.com/a/38577549/1048572) to the instance. – Bergi Nov 28 '16 at 16:41
  • @Bergi Next time you may wish to actually try the code before you comment as your suggestion to use `.toBufferAsync()` does not work for the reason I provided in my answer. – Rob Raisch Nov 28 '16 at 22:22
  • @RobRaisch Of course [`toBufferAsync` does work](http://stackoverflow.com/a/39979778/1048572) (given that `toBuffer` takes a callback), I don't see any reasoning in your answer why it would not? – Bergi Nov 28 '16 at 22:29
  • @Bergi, perhaps you'd like to provide a working example of using bluebird `promisifyAll()`? – Rob Raisch Nov 28 '16 at 22:31
  • @RobRaisch Have a look at the linked answer. Does that not work for you? – Bergi Nov 28 '16 at 22:38

1 Answers1

4

That's not really how you're supposed to use the gm module as it is designed to provide a factory gm() and a number of cascadeable mutator functions like .resize().

Bluebird's .promisifyAll() only works on functions that accept a callback, which the majority of gm's functions do not.

If you wish to use gm with promises, you'll need to "promisify" it yourself, by wrapping your call as

function mutateAndSave() {
  return new Promise( function(resolve,reject) {
    try {
      gm(image)
        .doSomething()
        .write(outputPath, function(err) {
          if(err) {
            throw(err);
          }
          resolve();
        });
    }
    catch (err) {
      reject(err);
    }
  });
}

After which, you can

mutateAndSave()
  .then(...)
  .catch(...);

UPDATE

Here are two ways to do what you want, but...

You'll note that both are a lot more complicated than just using gm as it is intended. ;)

Here's a way to do what you want with an event state machine.

const gm = requre('gm');
const EventEmitter = require('events');
const input_path = './image.jpg'
const output_path = './newimage.jpg';
const worker = new EventEmitter(); // create an event worker

// define the worker states - note: NO error checking! Muy mal! 
const event_states={
  start:()       => worker.emit('resize',gm(input_path)),       // creates a new gm image instance
  resize:(img)   => worker.emit('crop', img.resize(100,100)),   // resizes the image
  crop:(img)     => worker.emit('write', img.crop(2,2,50,50)),  // crops it
  write:(img)    => {                                           // and writes it to the filesystem
    img.write(output_path, err => {
      if(err) {
        worker.emit('error',err);
        return;
      }
      worker.emit('complete');
    });
  },
  error: (err)  => console.error(err.toString()),             // report error
  complete: ()  => console.log('complete')                    // alert on completion
};

// add the states to the worker as event handlers
Object.keys(event_states).forEach(k => worker.on(k, event_states[k]));

// and fire it up...
worker.emit('start'); 

Or if you really, REALLY want to use Promises...

const writer = function (img) {
  return new Promise( (resolve, reject) => {
    img.write(output_path,err => {
      if(err) {
        reject(err);
        return;
      }
      resolve(true);
    });
  });
};

const reader = function (input_path) {
  return new Promise( (resolve,reject) => { 
    let img;
    try {
      img = gm(input_path);
    }
    catch (err) {
      reject(err);
      return;
    }
    resolve(img);
  });
};

reader('./image.jpg')
  .then( img => { return img.resize(100,100) }) // the return here is for illustration only
  .then( img => img.crop(2,2,50,50))            // since this does exactly the same thing with less typing!
  .then( writer )
  .then( res => console.log('complete'))
  .catch( err => console.error(err.toString()));

Again, lots more typing and complexity all to use the newest "shiny" thing. ;)

Rob Raisch
  • 17,040
  • 4
  • 48
  • 58
  • At which point it seems much easier to just use the module as designed. – Paul Nov 26 '16 at 15:36
  • Actually, I want to perform over 10 operations on a single image buffer, so I was thinking in using `Promise.all([gmPromises])`. In this case, using promises really makes everything a lot more simple. – danielrvt Nov 27 '16 at 09:52
  • 1
    Yup...but gm isn't written to support Promises so you'll need to "find another way". :) – Rob Raisch Nov 28 '16 at 15:22
  • 1
    Using `try`/`catch` inside the `new Promise` callback is pointless. – Bergi Nov 28 '16 at 16:37
  • @Bergi True, but when instructing others it's important to be as clear and concise as possible, illustrating what is actually occurring rather than relying on non-obvious features. I disagree with your downvote as it assumes everyone who reads this understands Promises well. – Rob Raisch Nov 28 '16 at 22:07
  • 1
    @RobRaisch The downvote is not because the code is not concise enough, but because it does not work and implies wrong features. Asynchronous functions (should) never throw synchronously, so the `try`/`catch` is unnecessary. You need to call `reject` only when the callback is given an error - but you fail to do that and instead `throw` it (causing an `uncaughtException`). The only good promisification in your post is that of `writer`. – Bergi Nov 28 '16 at 22:20
  • @Bergi, could you point out in the gm source code where the gm factory function is async? – Rob Raisch Nov 28 '16 at 22:28
  • @RobRaisch [`gm` is a constructor that never throws](https://github.com/aheckmann/gm/blob/master/index.js#L20), so there's no need to wrap anything in a promise at all. You should simply do `writer(gm('./image.jpg').resize(100,100).crop(2,2,50,50)).then(…)` since none of these builder methods does anything asynchronous. – Bergi Nov 28 '16 at 22:37