Skip to content

Support promises #418

Description

@coodoo

LoopBack should provide Promise-enabled API out of the box. The plan is to use the approach outlined in nodejs/node#173.

Tasks


Below is the original proposal from @coodoo.

I've been playing around with bluebird's promisifyAll() for a while, ended up I found it just take a couple of line to wrap the model and make it become async-compatible.

See code below:

var Promise = require("bluebird");

module.exports = function(ResetKey) {

// once a model is attached to the data source
ResetKey.on('dataSourceAttached', function( obj ){

    // wrap the whole model in Promise
   // but we need to avoid 'validate' method 
    ResetKey = Promise.promisifyAll( ResetKey, {filter: function(name, func, target){
      return !( name == 'validate');
    }} );
  })

}

Once the model is wrapped, we can use Promise syntax instead of callbacks:

// old way - callbacks
user.save(function(err, result){...})

// new way - promise
user.saveAsync()
.then(function(result){...})
.catch(function(err){...})

Ain't that lovely? :)

Of course it would be even better if all those happened higher up in the chain and become default.

Thought?

Activity

  1. wqli78 commented on Aug 1, 2014

    @wqli78

    +1

  2. glesage commented on Aug 3, 2014

    @glesage

    +1

  3. wpjunior commented on Aug 3, 2014

    @wpjunior

    +1

  4. offlinehacker commented on Aug 3, 2014

    @offlinehacker
    Contributor

    +1
    On Aug 3, 2014 12:41 PM, "Wilson Júnior" notifications@github.com wrote:

    +1

    —
    Reply to this email directly or view it on GitHub
    #418 (comment).

  5. nowherenearithaca commented on Aug 3, 2014

    @nowherenearithaca

    This might be of interest to folks - talk on InfoQ about large nodejs project at kixeye where Paul Hill discusses why they liked Promises - http://www.infoq.com/presentations/kixeye-rest-api. While there is a ton of useful info in this talk, he specifically discusses Promises (they used Bluebird) starting at about 21:50. Besides the syntactic sugar/readability, he highlights how there is an "implicit try-catch" that allows centralizing error handling/logging. He also shows a code snippet (25:51) for how they did this that might be a useful pattern to consider including in the docs at some point, if possible.

  6. raymondfeng commented on Aug 3, 2014

    @raymondfeng
    Member

    Thank you all for the input. We're building a list of roadmap items at https://github.com/strongloop/loopback/wiki/Roadmap-brainstorming. 'Promise' is on the list too. Maybe we should move it to a gist so that community ideas can be accepted via PRs.

  7. reopened this on Aug 5, 2014
  8. partap commented on Nov 18, 2014

    @partap

    I've actually been using something similar...after boot(app), insert:

    Promise   = require 'bluebird'
    bbutil    = require 'bluebird/js/main/util'
    for model, p of app.models
      Promise.promisifyAll p, filter: (name, func, target) ->
        return bbutil.isIdentifier(name) &&
          !bbutil.isClass(func) &&
          name.charAt(0) isnt '_' &&
          name.indexOf('Async') is -1 &&
          name isnt 'app'
    

    ...pardon my coffeescript :)

    This will promisify all the app's models at once. I have a couple more items in my filter to exclude attributes we don't need to promisify, but it basically works the same.

    The main problem is that you can't use the promises in a boot script or model init script, but I think @coodoo takes care of that with his solution above (thanks!). You can use @coodoo's method for complex models, and then use the app.models loop to take care of the rest (it's ok to run promisifyAll() twice on the same object with this filter.

    The other problem I've found is that it doesn't work for the model relations. For example:

    user.posts.createAsync(...)
    

    will fail...I'm not sure how to go about promisifying those "virtual selectors"
    I'm not sure how or where they are defined or attached...

  9. partap commented on Nov 18, 2014

    @partap

    I was able to pause in the debugger and drill down a bit, but I'm still confused. There's some sort of convention for defining these methods that I'm not familiar with.

    I'm looking at a user instance in the debugger...I'll try to asciify it :

    user 
    | ModelConstructor
    |  __cachedRelations: Object
    |  __dataSource: Object
    | |__data: Object
    | |  email: "joe@schmoe.com"
    | |  id: ObjectId
    | |  ...
    | ...
    | |__proto__: ModelConstructor
    | |  __count__accessTokens: function (cb) {
    | |  __create__accessTokens: function () {
    | |  __delete__accessTokens: function () {
    | |  ...
    

    So if I understand this at all, it seems that anytime I access a property or method of a model instance, there's some javascript magic going on...user.id gets translated to user.__data.id, user.accessTokens.count() calls user.__count__accessTokens(), etc.

    I saw similarly named methods in the lb-services.js file generated for angularjs...

    ...What is this magic and how does it work? :)

  10. raymondfeng commented on Nov 19, 2014

    @raymondfeng
    Member

    The methods for relations are added to the model as properties with getters. For example, user.accessTokens will return a function bound to the user instance. The function itself also has other methods such as ‘create’, ‘count’, and ‘delete’. Please note the ____accessTokens methods are added to the User.prototype for legacy remoting purposes. These methods will probably be removed from the model prototype in the future.

    Thanks,


    Raymond Feng
    Co-Founder and Architect @ StrongLoop, Inc.

    StrongLoop http://strongloop.com/ makes it easy to develop APIs http://strongloop.com/mobile-application-development/loopback/ in Node, plus get DevOps capabilities http://strongloop.com/node-js-performance/strongops/ like monitoring, debugging and clustering.

    On Nov 18, 2014, at 3:58 PM, Partap Davis notifications@github.com wrote:

    I was able to pause in the debugger and drill down a bit, but I'm still confused. There's some sort of convention for defining these methods that I'm not familiar with.

    I'm looking at a user instance in the debugger...I'll try to asciify it :

    user
    | ModelConstructor
    | cachedRelations: Object
    | __dataSource: Object
    | |__data: Object
    | | email: "joe@schmoe.com"
    | | id: ObjectId
    | | ...
    | ...
    | |__proto
    : ModelConstructor
    | | __count__accessTokens: function (cb) {
    | | __create__accessTokens: function () {
    | | __delete__accessTokens: function () {
    | | ...
    So if I understand this at all, it seems that anytime I access a property or method of a model instance, there's some javascript magic going on...user.id gets translated to user.__data.id, user.accessTokens.count() calls user.__count__accessTokens(), etc.

    I saw similarly named methods in the lb-services.js file generated for angularjs...

    ...What is this magic and how does it work? :)

    —
    Reply to this email directly or view it on GitHub #418 (comment).

  11. 100 remaining items

  12. stale commented on Aug 23, 2017

    @stale

    This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

  13. bajtos commented on Aug 23, 2017

    @bajtos
    Member

    @kjdelisle @raymondfeng I think we may need to address this issue as part of our work on LoopBackNext. At least the parts in juggler (e.g. scope methods), because we are reusing juggler in LoopBackNext so far.

  14. stale commented on Oct 22, 2017

    @stale

    This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

  15. stale commented on Nov 5, 2017

    @stale

    This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.

  16. reopened this on Feb 9, 2018
  17. stale commented on Apr 10, 2018

    @stale

    This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

  18. stale commented on Apr 24, 2018

    @stale

    This issue has been closed due to continued inactivity. Thank you for your understanding. If you believe this to be in error, please contact one of the code owners, listed in the CODEOWNERS file at the top-level of this repository.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions