Метеор: правильное использование Meteor.wrapAsync на сервере

Фон

Я пытаюсь интегрировать платежи полосы на мой сайт. Мне нужно создать пользователя с полосой, используя мой секретный ключ. Я сохраняю этот ключ на своем сервере и вызываю метод сервера для создания пользователя. Может быть, есть еще один способ сделать это? Здесь strip api (скопировано ниже для удобства): 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
});

Мои попытки и результаты

Я использовал один и тот же код клиента с другим кодом сервера. Все попытки немедленно дают undefined на клиенте console.log(...), но дают правильный ответ на сервере 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
        }));
    }
});

Я также пробовал оба без Meteor.wrapAsync.

EDIT - я также использую этот пакет: https://atmospherejs.com/mrgalaxy/stripe

Ответы

Ответ 1

Из Meteor.wrapAsync http://docs.meteor.com/#meteor_wrapasync вы можете увидеть, что вам нужно передать ему функцию и, возможно, контекст, тогда как на ваших двух вы пытаетесь передать РЕЗУЛЬТАТ вызова асинхронной версии 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 преобразует асинхронную функцию в удобную синхронную функцию, которая позволяет писать последовательно выглядящий код. (underhood все еще выполняется в асинхронном цикле событий Node.js).

Нам нужно перейти на Meteor.wrapAsync нашу функцию API (Stripe.customers.create) вместе с контекстом функции, т.е. this внутри тела API-функции, которая в этом случае Stripe.customers.

ИЗМЕНИТЬ:

Как получить ошибки?

Традиционные функции API стиля node обычно принимают обратный вызов как последний аргумент, который в конечном итоге будет вызван, когда требуемая задача будет завершена. Этот обратный вызов принимает 2 аргумента: ошибка и данные, либо один будет иметь значение null в соответствии с результатом вызова.

Как мы получаем доступ к объекту ошибки с помощью синхронных завернутых функций, возвращаемых Meteor.wrapAsync?

Мы должны полагаться на использование блоков try/catch, потому что в случае ошибки он будет вызываться функцией синхронизации, а не передаваться как первый аргумент обратного вызова функции async.

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

почему не нужно передавать Stripe?

JavaScript не имеет понятия "пространства имен", поэтому разработчики API используют общий трюк определения глобального объекта, выступающего в качестве пространства имен API, свойства, определенные на этом объекте, являются "подмодулями" API. Это означает, что Stripe.customers является подмодулем API-интерфейса Stripe, предоставляющим связанные с клиентом функции, и поэтому такие контексты this this Stripe.customers, а не Stripe.

Вы можете протестировать его самостоятельно, скопировав вставляя этот издевательский код в консоль вашего браузера:

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

И затем вызов функции заглушки в консоли вашего браузера следующим образом:

> Stripe.customers.create();
true

Ответ 2

Другим вариантом является пакет, который достигает аналогичных целей.

meteor add meteorhacks:async

Из пакета README:

Async.wrap(функция)

Оберните асинхронную функцию и разрешите ее запускать внутри Meteor без обратных вызовов.

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

Ответ 3

Прежде всего, благодаря @saimeunt для его ответа, что делает некоторые сложные концепции понятными. Однако у меня возникла проблема с необходимостью классического обратного вызова aync (ошибка, результат), отображающего как ошибку, так и результат на клиенте, чтобы я мог давать информационные сообщения в браузере.

Я решил это так:

Код сервера:

var Stripe = StripeAPI(STRIPE_SECRET_KEY);

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

Клиентский код:

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

Выглядит аккуратно. Однако, к сожалению, wrapAsync имеет открытую ошибку (см. https://github.com/meteor/meteor/issues/2774), поскольку он не восстанавливает правильную ошибку для вызывающего. Гений под названием Faceyspacey написал замену, названную Meteor.makeAsync(), которую вы найдете на странице ошибки, о которой я упомянул, которая, однако, возвращает либо результат ИЛИ ошибку переменной "result", оставляя переменную "ошибка" undefined, На данный момент это замечательно, по крайней мере, у меня есть крючок на соответствующем объекте ошибки.

Если вы используете makeAsync(), вам нужно будет импортировать Futures следующим образом:

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

Ответ 4

Поскольку вам понадобится почти каждая функция, которую нужно использовать в Async, что вам нужно сделать, это использовать этот пакет https://atmospherejs.com/copleykj/stripe-sync, который предохраняет все функции stripe от WrapAsync, делая вашу жизнь проще и чище кода.