Meteor: właściwe użycie meteoru.wrapasync na serwerze

Tło

Próbuję zintegrować płatności stripe z moją stroną. Muszę utworzyć użytkownika stripe używając mojego prywatnego klucza stripe. Przechowuję ten klucz na moim serwerze i wywołuję metodę serwera, aby utworzyć użytkownika. Może jest inny sposób na osiągnięcie tego? Oto API stripe (skopiowane poniżej dla wygody): 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
});

Moje próby i wyniki

Używam tego samego klienta kod z innym kodem serwera. Wszystkie próby natychmiast dają undefined na konsoli klienta.log(...), ale dać właściwą odpowiedź na konsoli serwera.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
        }));
    }
});
Próbowałem też obu bez Meteor.wrapAsync.

EDIT-ja również korzystam z tego pakietu: https://atmospherejs.com/mrgalaxy/stripe

Author: Brett McLain, 2014-10-07

4 answers

Z Meteor.wrapAsync http://docs.meteor.com/#meteor_wrapasync widzisz, że musisz przekazać mu funkcję i opcjonalnie kontekst, podczas gdy podczas dwóch prób przekazujesz wynik wywołania asynchronicznej wersji 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 przekształca funkcję asynchroniczną w wygodną funkcję synchroniczną, która umożliwia pisanie sekwencyjnie wyglądającego kodu. (underhood wszystko jest nadal wykonywane w obrębie węzła asynchronicznego.js event loop).

We trzeba przejść do Meteor.wrapAsync naszej funkcji API (Stripe.customers.create) wraz z kontekstem funkcji, czyli this wewnątrz ciała func API, który w tym przypadku jest Stripe.customers.

EDIT:

Jak odzyskać błędy ?

Tradycyjne funkcje API w stylu węzłów zwykle przyjmują wywołanie zwrotne jako ostatni argument, który zostanie wywołany ostatecznie po zakończeniu wymaganego zadania. To wywołanie zwrotne pobiera 2 argumenty: błąd i dane, albo jeden będzie null zgodnie z wynikiem sprawdzam.

Jak uzyskać dostęp do obiektu błędu przy użyciu funkcji zawiniętych synchronicznie zwracanych przez Meteor.wrapAsync?

Musimy polegać na użyciu bloków try/catch, ponieważ w przypadku błędu zostanie on wyrzucony przez funkcję sync zamiast być przekazywany jako pierwszy argument funkcji asynchronicznej.

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);
});

Dlaczego Stripe nie musi być przekazywane?

JavaScript nie ma pojęcia "przestrzeni nazw" , więc deweloperzy API używają wspólnej sztuczki definiowania globalnego obiekt pełniący funkcję przestrzeni nazw API, właściwości zdefiniowane w tym obiekcie są "podmodułami" API. Oznacza to, że Stripe.customers jest podmodułem interfejsu API Stripe, eksponującym funkcje związane z klientami, i jako takie te funkcje this kontekstem jest Stripe.customers, a nie Stripe.

Możesz go przetestować samodzielnie, kopiując ten szyderczy kod w konsoli przeglądarki:

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

A następnie wywołanie funkcji stub w konsoli przeglądarki w następujący sposób:

> Stripe.customers.create();
true
 73
Author: saimeunt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-20 11:50:25

Inną opcją jest pakiet , który osiąga podobne cele.

meteor add meteorhacks:async

Z pakietu README:

Async.wrap (function)

owinąć funkcję asynchroniczną i umożliwić jej uruchomienie wewnątrz Meteor bez wywołania zwrotnego.

//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
Author: FullStack,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-13 03:52:20

Po pierwsze, dzięki @saimeunt za odpowiedź, która wyjaśnia niektóre trudne pojęcia. Jednak miałem problem z chce klasyczny asynchroniczny callback (err, wynik) pokazując zarówno błąd, jak i wynik z powrotem na kliencie, tak, że mogę dać informacyjne wiadomości w przeglądarce.

Rozwiązałem to w ten sposób:

Kod serwera:

var Stripe = StripeAPI(STRIPE_SECRET_KEY);

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

Kod klienta:

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);
});
Wygląda schludnie. Jednak niestety wrapAsync ma otwarty błąd, (zobacz https://github.com/meteor/meteor/issues/2774 ), ponieważ nie przywraca WŁAŚCIWEGO błędu do wywołującego. Geniusz zwany Faceyspacey napisał zamiennik o nazwie Meteor.makeAsync (), którą znajdziesz na stronie błędu, o której wspomniałem, która jednak zwraca wynik lub błąd do zmiennej 'result', pozostawiając zmienną' error ' niezdefiniowaną. Na razie mi to nie przeszkadza, przynajmniej Mam Haka na właściwy obiekt błędu.

Jeśli używasz makeAsync (), musisz Importuj Futures tak:

Meteor.startup(function () {
    //this is so that our makeAsync function works
    Future = Npm.require('fibers/future');
});
 7
Author: mwarren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 15:21:48

Ponieważ będziesz potrzebował prawie każdej funkcji do owinięcia w Asynchronizację, powinieneś użyć tego pakietu https://atmospherejs.com/copleykj/stripe-sync, który prewrapuje wszystkie funkcje stripe za pomocą WrapAsync, co ułatwi Ci życie i oczyści kod.

 1
Author: Patrick Mencias-lewis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-10 20:22:06