web-dev-qa-db-fra.com

Meteor: utilisation appropriée de Meteor.wrapAsync sur le serveur

Contexte

J'essaie d'intégrer les paiements par bande dans mon site. Je dois créer un utilisateur Stripe à l'aide de ma clé Stripe privée. Je stocke cette clé sur mon serveur et j'appelle une méthode serveur pour créer l'utilisateur. Peut-être qu'il y a une autre façon d'accomplir cela? Voici l'API Stripe (copiée ci-dessous pour plus de commodité): https://stripe.com/docs/api/node#create_customer

//stripe api call
var Stripe = StripeAPI('my_secret_key');

Stripe.customers.create({
  description: 'Customer for [email protected]',
  card: "foobar" // obtained with Stripe.js
}, function(err, customer) {
  // asynchronously called
});

Mes tentatives et résultats

J'utilise le même code client avec un code serveur différent. Toutes les tentatives donnent immédiatement undefined sur le client console.log (...) mais donnent la bonne réponse sur le serveur console.log (...):

//client
Meteor.call('stripeCreateUser', options, function(err, result) {
  console.log(err, result);
});

//server attempt 1
var Stripe = StripeAPI('my_secret_key');

Meteor.methods({
    stripeCreateUser: function(options) {  
        return Meteor.wrapAsync(Stripe.customers.create({
            description: 'Woot! A new customer!',
            card: options.ccToken,
            plan: options.pricingPlan
        }, function (err, res) {
            console.log(res, err);
            return (res || err);
        }));
    }
});

//server attempt 2
var Stripe = StripeAPI('my_secret_key');

Meteor.methods({
    stripeCreateUser: function(options) {  
        return Meteor.wrapAsync(Stripe.customers.create({
            description: 'Woot! A new customer!',
            card: options.ccToken,
            plan: options.pricingPlan
        }));
    }
});

J'ai également essayé les deux sans Meteor.wrapAsync.

EDIT - J'utilise également ce package: https://atmospherejs.com/mrgalaxy/stripe

50
Adam

Du Meteor.wrapAsynchttp://docs.meteor.com/#meteor_wrapasync vous pouvez voir que vous devez lui passer une fonction et éventuellement un contexte, tandis que lors de vos deux tentatives, vous passez le RÉSULTAT d'appeler le version asynchrone de Stripe.customers.create.

Meteor.methods({
  stripeCreateUser: function(options) {
    // get a sync version of our API async func
    var stripeCustomersCreateSync=Meteor.wrapAsync(Stripe.customers.create,Stripe.customers);
    // call the sync version of our API func with the parameters from the method call
    var result=stripeCustomersCreateSync({
      description: 'Woot! A new customer!',
      card: options.ccToken,
      plan: options.pricingPlan
    });
    // do whatever you want with the result
    console.log(result);
  }
});

Meteor.wrapAsync transforme une fonction asynchrone en une fonction d'aspect synchrone pratique qui permet d'écrire du code d'aspect séquentiel. (underhood tout est toujours exécuté dans la boucle d'événement asynchrone Node.js).

Nous devons passer à Meteor.wrapAsync notre fonction API (Stripe.customers.create) avec le contexte de la fonction, c'est-à-dire this à l'intérieur du corps de la fonction API, qui dans ce cas est Stripe.customers.

MODIFIER :

Comment récupérer des erreurs?

Les fonctions API de style nœud traditionnelles prennent généralement un rappel comme dernier argument qui sera finalement appelé lorsque la tâche requise sera terminée. Ce rappel prend 2 arguments: erreur et données, l'un ou l'autre sera nul en fonction du résultat de l'appel.

Comment pouvons-nous accéder à l'objet d'erreur à l'aide des fonctions enveloppées synchrones renvoyées par Meteor.wrapAsync?

Nous devons compter sur l'utilisation de blocs try/catch, car en cas d'erreur, il sera jeté par la fonction de synchronisation au lieu d'être passé comme premier argument du rappel de la fonction asynchrone.

try{
  var result=syncFunction(params);
  console.log("result :",result);
}
catch(error){
  console.log("error",error);
}
// is the equivalent of :
asyncFunc(params,function(error,result){
  if(error){
    console.log("error",error);
    return;
  }
  console.log("result :",result);
});

pourquoi Stripe n'a-t-il pas besoin d'être adopté?

JavaScript n'a pas de concept "d'espace de noms", donc les développeurs d'API utilisent l'astuce courante de définir un objet global agissant comme espace de noms d'API, les propriétés définies sur cet objet sont des "sous-modules" de l'API. Cela signifie que Stripe.customers est un sous-module de l'API Stripe exposant les fonctions liées aux clients, et en tant que telles ces fonctions this le contexte est Stripe.customers, pas Stripe.

Vous pouvez le tester vous-même en copiant-collant ce code moqueur dans la console de votre navigateur:

Stripe={
  customers:{
    create:function(){
      console.log(this==Stripe.customers);
    }
  }
};

Et puis appeler la fonction stub dans votre console de navigateur comme ceci:

> Stripe.customers.create();
true
74
saimeunt

Une autre option est ceci package qui atteint les mêmes objectifs.

meteor add meteorhacks:async

À partir du package README:

Async.wrap (fonction)

Enveloppez une fonction asynchrone et autorisez-la à être exécutée à l'intérieur de Meteor sans rappel.

//declare a simple async function
function delayedMessge(delay, message, callback) {
  setTimeout(function() {
    callback(null, message);
  }, delay);
}

//wrapping
var wrappedDelayedMessage = Async.wrap(delayedMessge);

//usage
Meteor.methods({
  'delayedEcho': function(message) {
    var response = wrappedDelayedMessage(500, message);
    return response;
  }
});
11
FullStack

Tout d'abord, merci à @saimeunt pour sa réponse, qui clarifie certains concepts difficiles. Cependant, j'ai eu le problème de vouloir un rappel asynchrone classique (err, résultat) montrant à la fois l'erreur et le résultat sur le client, afin que je puisse donner des messages informatifs dans le navigateur.

Je l'ai résolu de cette façon:

Code serveur:

var Stripe = StripeAPI(STRIPE_SECRET_KEY);

Meteor.methods({
    createCust: Meteor.wrapAsync(Stripe.charges.create, Stripe.charges)
});

Code client:

var stripeCallOptions = {
    description: 'Woot! A new customer!',
    card: ccToken,
    plan: pricingPlan
};


Meteor.call('createCust', stripeCallOptions, function(error, result){
    console.log('client error', error);
    console.log('client result', result);
});

Ça a l'air bien. Cependant, malheureusement, wrapAsync a un bogue ouvert, (voir https://github.com/meteor/meteor/issues/2774 ) car il ne restaure pas l'erreur appropriée à l'appelant. Un génie appelé Faceyspacey a écrit un remplaçant appelé Meteor.makeAsync () que vous trouverez sur la page de bogue que j'ai mentionnée, qui renvoie cependant soit le résultat OR l'erreur dans la variable 'result', laissant la variable "erreur" indéfinie. C'est bien pour moi pour l'instant, au moins j'ai un crochet sur l'objet d'erreur approprié.

Si vous utilisez makeAsync (), vous devrez importer des Futures comme ceci:

Meteor.startup(function () {
    //this is so that our makeAsync function works
    Future = Npm.require('fibers/future');
});
7
mwarren

Comme vous allez avoir besoin que presque toutes les fonctions soient encapsulées dans Async, vous devez utiliser ce package https://atmospherejs.com/copleykj/stripe-sync il pré-enveloppe toutes les fonctions de bande avec WrapAsync pour vous faciliter la vie et nettoyer le code.

1