How to call a Promise from a Meteor ValidatedMethod

Hello,

I am using the sharp npm module to resize a png image that I am saving into a mongo collection.
The sharp module returns a promise, please see my code.
I make 2 queries to the db, one to update the doc and another when the sharp module promise resolves to update the image.

How can I make it in one call?
Thanks

export const insertMeme = new ValidatedMethod({
  name: 'memes.insert',
  validate: memeSchema.validator({ clean: true, filter: true }),
  run(document) {

    const _id = Memes.insert(document);
    if (Meteor.isServer) {
      const base64string = document.image.replace('data:image/png;base64,', '');
      const sharp = require('sharp');
      sharp(Buffer.from(base64string, 'base64'))
        .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
        .resize({ width: 500 })
        .toBuffer()
        .then(buffer => {
          document.image = 'data:image/png;base64,' + buffer.toString('base64');
          Memes.update(document._id, {
            $set: document
          });
        })
        .catch(err => {
          console.error(err);
        });
    }

    return _id;
  },
});

I suggest doing it this way:

export const insertMeme = new ValidatedMethod({
  name: 'memes.insert',
  validate: memeSchema.validator({ clean: true, filter: true }),
  async run(document) {
    const _id = Memes.insert(document);

    if (Meteor.isServer) {
      const base64string = document.image.replace('data:image/png;base64,', '');
      const sharp = require('sharp');

      const image = await sharp(Buffer.from(base64string, 'base64'))
      .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
      .resize({ width: 500 })
      .toBuffer();

      document.image = 'data:image/png;base64,' + buffer.toString('base64');

      Memes.update(_id, {
        $set: document
      });
    }

    return _id;
  },
});

But I also suggest you run this method only on the server. Still there is nothing wrong in doing it this way.

Hhope this helps

1 Like

Thanks for the reply, your code make sense.
I tried async/await in meteor but it doesn’t work.
I am missing something.

Let’s see your code :slight_smile:

1 Like

async/await in Meteor is basically async/await in JavaSript. If you want to call a validated-method as a promise take a look at https://github.com/didericis/callpromise-mixin/.

Rob, it is the same from pmogollon.

Looking at the code from @pmogollon, which I think has a typo, along with your original request to write to the db with one call, can you try this:

export const insertMeme = new ValidatedMethod({
  name: 'memes.insert',
  validate: memeSchema.validator({ clean: true, filter: true }),
  async run(document) {
    if (Meteor.isServer) {
      const base64string = document.image.replace('data:image/png;base64,', '');
      const sharp = require('sharp');

      const image = await sharp(Buffer.from(base64string, 'base64'))
        .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
        .resize({ width: 500 })
        .toBuffer();

      document.image = 'data:image/png;base64,' + image.toString('base64');

      const _id = Memes.insert(document);
    }

    return _id;
  },
});

Incidentally, using document is a bit risky in the browser, as it’s reserved for the HTML document. Better to use doc, maybe.

1 Like

Thank you @robfallows and @pmogollon
pmogollon code actually works, maybe yesterday I was too tired to notice.

The following code works for me and it is using document as variable,
I think it is not a problem because it is a function argument.

Only one call to the db.

export const insertMeme = new ValidatedMethod({
  name: 'memes.insert',
  validate: memeSchema.validator({ clean: true, filter: true }),
  async run(document) {
    if (Meteor.isServer) {
      const base64string = document.image.replace('data:image/png;base64,', '');
      const sharp = require('sharp');
      const buffer = await sharp(Buffer.from(base64string, 'base64'))
        .png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
        .resize({ width: 500 })
        .toBuffer();
      document.image = 'data:image/png;base64,' + buffer.toString('base64');
    }
    return Memes.insert(document);
  },
});

@robfallows @pmogollon @softwarerero
The above code works with async await but now I can not throw exceptions from the async function.
I need to throw some exceptions that are display as error in red on the UI page.
How can I throw exceptions from the async function, the following does not work

I get the following error:
1 Uncaught (in promise) {error: “validation-error”, details: Array(1)}

    if (!(/\b______\b/g).test(document.topText) && !(/\b______\b/g).test(document.bottomText)) {
      throw {
        error: 'validation-error',
        details: [{
          type: 'blankRequired',
          name: 'topText',
        }]
      };
    }

You need to throw new Meteor.Error to ensure a server error is sent to the client.

2 Likes