Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise version? #64

Open
orecus opened this issue Dec 12, 2016 · 12 comments
Open

Promise version? #64

orecus opened this issue Dec 12, 2016 · 12 comments

Comments

@orecus
Copy link

orecus commented Dec 12, 2016

Love this library, but I was just wondering if there have been any thoughts about moving to promises for this?

@impronunciable
Copy link
Owner

That'd be cool. It should be easy to wrap 🤔

@orecus
Copy link
Author

orecus commented Dec 12, 2016

Yes, I thought about wrapping it myself, but thought I would ask here before I started looking at it if it where planned. I think it would be great instead of wrapping it myself. :)

My thought was to use it together with promise-queue to get some form of control on the amount of requests being sent trough the various calls, it would be great if it would follow the api-limits.

@impronunciable
Copy link
Owner

@orecus I think is a good idea. If you want to implement it go ahead and I can review it and help

@orecus
Copy link
Author

orecus commented Dec 13, 2016

@impronunciable I'll try to take a look at it in the coming days and see what I can do. :)

Some alternatives for some form of rate limiting: https://github.com/JMPerez/promise-throttle and https://github.com/azproduction/promise-queue could work perhaps if you would wish to implement rate limiting in the plugin itself.

@orecus
Copy link
Author

orecus commented Jan 3, 2017

Sadly I havn't had time too look at the code to try to implement this nativly within MovieDB. I solved this by simply wrapping the calls with moviedb as promises and using https://github.com/JMPerez/promise-throttle, it does seem to work perfectly fine.

@impronunciable
Copy link
Owner

Great, is there any code I can take a look at?

@orecus
Copy link
Author

orecus commented Jan 3, 2017

Sure, here are a quick and dirty example from the code i have, it will be available in full once the application i'm writing is in a shape to be shared. :)

You call searchMovie and that will initiate a throttled API call, from my quick tests it seems to work fine.

const PromiseThrottle = require('promise-throttle');
const throttle        = new PromiseThrottle({ requestsPerSecond: 4, promiseImplementation: Promise });
const mdb             = require('moviedb')(tmdbAPIKey);

  function internal_searchMovie(searchData) {
    let query = internal_searchTerm(searchData);

    return new Promise(function (resolve, reject){
      mdb.searchMovie(query, function(error, searchResults){
        if (error) {
          return reject({error: error.response.body.errors[0], type: 'TMDB Error'});
        } else {
          return resolve(searchResults.results);
        }
      });
    });
  }

  function searchMovie(data, respond) {
    if (data.id != undefined) {
      respond(null, data);
    } else {
      throttle.add(internal_searchMovie.bind(this, data))
      .then((searchResults) => {
        respond(null, searchResults);
      })
      .catch((error) => {
        respond(null, {error: error});
      });
    }
  }

@staaky
Copy link

staaky commented Jan 20, 2017

Here's a quick little wrapper that supports all API calls.

// moviedbp.js
var MovieDB = require('moviedb');
var endpoints = require('moviedb/lib/endpoints.json');
var limits = require('limits.js');

// rate limiting
var queue = limits();
queue.within(1000, 4);

function MovieDBP(api_key, base_url) {
  this.moviedb = MovieDB(api_key, base_url);
  return this;
}

Object.keys(endpoints.methods).forEach(function(method) {
  var met = endpoints.methods[method];
  Object.keys(met).forEach(function(m) {
    MovieDBP.prototype[method + m] = function(params) {
      var self = this;
      if (typeof params == "function") {
        params = {};
      }

      return new Promise(function(resolve, reject) {
        queue.push(function() {
          self.moviedb[method + m](params, function(err, res) {
            if (err) return reject(err);
            resolve(res);
          });
        });
      });
    };
  });
});

module.exports = function(api_key, base_url) {
  return new MovieDBP(api_key, base_url);
};
const mdbp = require('moviedbp')('your api key');

mdbp.searchMovie({ query: 'Alien' })
.then((res) => { console.log(res); })
.catch((err) => { console.log(err); });

Maybe an option to switch between callbacks and Promises would be nice? It breaks chaining but that's something you could document. Something like:

require('moviedb')({ api_key: 'your api key', promise: true });

The rate limiting in my snippet could be used for both.

@daviestar
Copy link

Promise wrap:

const _mdb = require('moviedb')('your-key')

const mdb = (m, q) => new Promise((res, rej) => {
  _mdb[m](q, (err, data) => err ? rej(err) : res(data))
})

mdb('searchMovie', {query: 'Alien'})
  .then(console.log)
  .catch(console.error)

With limits:

const _mdb = require('moviedb')('your-key')
const limits = require('limits.js')
const throttle = limits().within(1000, 3)

const mdb = (m, q) => new Promise((res, rej) => {
  throttle.push(() => _mdb[m](q, (err, data) => err ? rej(err) : res(data)))
})

mdb('searchMovie', {query: 'The Martian', year: 2015})
  .then(console.log)
  .catch(console.error)

@favna
Copy link

favna commented Jan 2, 2018

Through "testing" I see you ended up implementing the method as proposed by @daviestar but you haven't updated the readme yet, might I recommend doing that as well as closing the issue so it's clear to people that promises are available?

Also to add some code feedback to this general feedback, the promises method also allows for an async/await approach:

const moviedb = require('moviedb')(auth.TheMovieDBV3ApiKey);

async run(msg, args) {

    const tmdb = (m, q) => new Promise((res, rej) => {
            moviedb[m](q, (err, data) => err ? rej(err) : res(data)); // eslint-disable-line no-confusing-arrow
        }),
        tmdbres = await tmdb('searchMovie', {
            'query': args.name
        });

    if (tmdbres) {
        return console.log(tmdbres);
    }

    return console.log('error');
}

Edit: a full implementation of the code above can be seen here, which is for my Discord bot: https://gist.github.com/Favna/5951fe0760553e99c0db139794ee4228

@daviestar
Copy link

@favna I don't think a promise implementation has made it to this repo yet, but as you've already done it's easy to wrap it.

When using async/await be careful to manually catch any promise rejection:

try {
  const tmdbres = await tmdb('searchMovie', {...})
  if (tmdbres) {
    return console.log(tmdbres)
  }
  return console.log('no response')
} catch (err) {
  return console.error(err)
}

Using the promise wrapper above is great for one-off searches but if you intend to loop through a big list of movies and grab metadata, you should take a look at the slightly more complicated implementation here: #74 (comment)

@mocanew
Copy link

mocanew commented Jan 2, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants