javascriptnode.jsasynchronouspromisegm

NodeJS/gm: Promises with asynchronous calls


I'm using gm to manipulate some images in my nodeJS application. This is how my function looks like.

As you can see there is a switch for some manipulation (in this example: rotation). gmStream is created and after the switch .stream() and pipe() will be used. So far everything is fine.

But for the switch case resize, I need to know the dimensions of the image, which I do get via size(). But this is an asynchronous call. With this gmStream isn't used for the stream() shown below the switch. But there will be done some more DB stuff in this stream(), so I need to use the same thing...

function manipulate (method, param) {
  return new Promise(function (resolve, reject) {

    // Configure GridFS (gridfs-stream)
    const gfs = Grid(
      MongoInternals.defaultRemoteCollectionDriver().mongo.db,
      MongoInternals.NpmModule
    )

    switch (method) {
      case 'rotate':
        gmStream = gm(readStream)
          .rotate('#ffffff', param.rotate)
        break

      // ... some more cases ...

      case 'resize':
        gmStream = gm(readStream)
          .size(function (err, size) {
            if (!err && size.width >= 1000 && size.height >= 1000) {
              gmStream.resize('1000').stream().pipe(writeStream) // <-- should use the stream call below, as there has to be done some DB manipulation...
            }
          })
        break
    }

    // resize case should also use this part...
    gmStream
      .stream(function (err, stdout, stderr) {
        gfs.findOne({ _id: sourceId }, function (err, file) {
          const writeStream = gfs.createWriteStream({
            metadata: { }
          })

          writeStream.on('close',
            function (newFile) {
              resolve(newFile)
            }
          )

          stdout.pipe(writeStream)
        })
      })
  })
}

Solution

  • You may want to chain Promises together to accomplish what you're after. If you break apart the logic in your switch case into Promise-returning functions, you might be able to get something like the following to work (disclaimer: I've never used gm and I'm not familiar with its API).

    function rotate (readStream, rotate) {
      return new Promise(function (resolve, reject) {
        resolve(gm(readStream).rotate('#ffffff', param.rotate))
      })
    }
    
    
    function resize (readStream, writeStream) {
      return new Promise(function (resolve, reject) {
        var gmStream = gm(readStream)
    
        gmStream.size(function (err, size) {
          if (err) {
            return reject(err)
          }
    
          if (size.width >= 1000 && size.height >= 1000) {
            gmStream.resize('1000').stream().pipe(writeStream)
            resolve(gmStream)
          }
        })
      })
    }
    
    
    function handleManipulation (args) {
      return new Promise(function (resolve, reject) {
        // This will be a Promise for the base gmStream object to work with
        var gmStream;
    
        // Not sure where this comes from, so here's a placeholder
        var readStream = ...;
    
        // You were doing this for every case, so I don't think putting it here
        // will cause you any grief, but it's a resource to clean up if the
        // Promise gets rejected, so keep that in mind
        const writeStream = gfs.createWriteStream({
          metadata: { }
        })
    
        // Figure out which method to create a Promise'd object for
        switch (args.method) {
          case 'rotate':
            gmStream = rotate(readStream, ...) // I'm not sure what the value of
                                               // the `rotate` argument should be
            break
          case 'resize':
            gmStream = resize(readStream, writeStream)
            break
        }
    
        // We wait for the gmStream Promise to resolve before proceeding.
        gmSteam.then(function (stream) {
          stream.stream(function (err, stdout, stderr) {
            if (err) {
              return reject(err)
            }
    
            gfs.findOne({ _id: sourceId }, function (err, file) {
              if (err) {
                return reject(err)
              }
    
              writeStream.on('close',
                function (newFile) {
                  resolve(newFile)
                }
              )
    
              stdout.pipe(writeStream)
            })
          })
        })
      })
    }