Метеор: правильное использование 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, делая вашу жизнь проще и чище кода.