Meteor with Promises

2019-05-14 01:02发布

问题:

I've been trying to get into the habit of using promises but ran into problems while trying to use them in server side code in the context of Meteor. This is the problem:

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup
    p = function(){
      return new Promise(function(res,rej) {
        res("asd");
      });
    };
    p().then(function(asd){
      console.log("asd is " + asd);
      return "zxc"
    }).then(Meteor.bindEnvironment(function(zxc){
      console.log("zxc is " + zxc);
      return "qwe"
    })).then(function(qwe){
      console.log("qwe is " + qwe);
    });
  });
}

mvrx:bluebird package is installed

code also available at GitHub

Expected output:

asd is asd         
zxc is zxc
qwe is qwe

Actual output:

asd is asd         
zxc is zxc
qwe is undefined

Dropping the Meteor.bindEnvironment wrapper fixes the problem, but i need it in order to be able to use Collections inside the callbacks

So what am i missing here? is it just not possible to use Promises + Meteor this way or is there a bug?

What i'm actually trying to accomplish is parallel pipelines that have important partial results but need a synchronized ending. Something like this.

if (Meteor.isServer) {
  Meteor.startup(function () {
    // code to run on server at startup


    promises = [];

     step1 = function(input){
        return new Promise(function(res, rej){
          console.log(input + ":Step 1");
          res(input);
        });
      };
      step2 = function(input){
        return new Promise(function(res, rej){
          console.log(input + ":Step 2");
          res(input);
        });
      };
      step3 = function(input){
        return new Promise(function(res, rej){
          console.log(input + ":Step 3");
          res(input);
        });
      };
      slowIO = function(input){
        var inp = input;
        return new Promise( function(res,rej){
          setTimeout(function(){
            console.log(inp + ":SlowIO");
            res(inp);
          },Math.random()*20000);
        });
      };
      end = function(input){
        return new Promise(function(res,rej){
          console.log(input + ": done, commiting to database");
          res()
        });
      };

    for (var i = 0; i < 100; ++i) {
      promises.push(step1("pipeline-" + i).then(step2).then(slowIO).then(step3).then(end));
    };

    Promise.all(promises).then(function(){
      console.log("All complete")
    });



  });
}

回答1:

(Update: I've logged an issue on github to see if it can be resolved.)

Looks like there's a issue with Meteor.bindEnvironment when used this way.

If it's called from outside a Fiber it won't return it's value. Notice the missing return before Fiber(runWithEnvironment).run()

A simple solution for the moment is to return a Promise instead of the result:

// when passed as a callback to `Promise#then`
//  allows it to resolve asynchronously
var asyncThen = function(fn){
  return function(arg){
    return new Promise(function(resolve, reject){
      fn(arg, resolve, reject);
    })
  };
};

Promise.resolve("asd").then(function(asd){
  console.log("asd is " + asd);
  return "zxc"
}).then(
  asyncThen(
    Meteor.bindEnvironment(function(zxc, resolve, reject){
      console.log("zxc is", zxc);
      resolve("qwe");
    })
  )
).then(function(qwe){
  console.log("qwe is " + qwe);
});


回答2:

A promise is just a way to write async code in a synchronous fashion. If that's all your after, why not use Meteor.wrapAsync()? In your case, you've got zxc riding cowboy in it's own fiber & who knows when it's coming back. Bluebird is great & super fast on the client, but I think the code is much cleaner using what Meteor gives you:

//*UNTESTED*//
asd = function() { return 'foo';};
asdSync = Meteor.wrapAsync(asd);
asdResult = asdSync();

qwe = function(input) {return input.reverse()};
qweSync = Meteor.wrapAsync(qwe);
qweResult = qweSync(asdResult); //should return 'oof'