Reputation: 29
I have a Cloud Function that is triggered when a document is created in Firestore, and I keep getting Function returned undefined, expected Promise or value
. The function does what it is supposed to do, but it sometimes takes around 25-30 seconds, so I thought it may have something to do with this error. I would really appreciate if someone could help me understand what to return here. My function is below:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const Iyzipay = require('iyzipay');
const iyzipay = new Iyzipay({
apiKey: '...',
secretKey: '...',
uri: '...'
});
exports.pay = functions
.region('europe-west1')
.firestore
.document('requests/{docId}')
.onCreate((snap, context) => {
const newValue = snap.data();
const request = {
locale: Iyzipay.LOCALE.TR,
conversationId: newValue.uid,
price: newValue.price,
paidPrice: newValue.price,
currency: Iyzipay.CURRENCY.TRY,
installment: '1',
basketId: 'B67832',
paymentChannel: Iyzipay.PAYMENT_CHANNEL.MOBILE_IOS,
paymentGroup: Iyzipay.PAYMENT_GROUP.LISTING,
paymentCard: {
cardHolderName: newValue.cardHolderName,
cardNumber: newValue.cardNumber,
expireMonth: newValue.expireMonth,
expireYear: newValue.expireYear,
cvc: newValue.cvc
},
buyer: {
id: newValue.uid,
name: newValue.name,
surname: newValue.surname,
gsmNumber: newValue.gsmNumber,
email: newValue.email,
identityNumber: newValue.identityNumber,
registrationAddress: newValue.registrationAddress,
city: newValue.city,
country: newValue.country,
zipCode: newValue.zipCode
},
shippingAddress: {
contactName: newValue.name,
city: newValue.city,
country: newValue.country,
address: newValue.registrationAddress,
zipCode: newValue.zipCode
},
billingAddress: {
contactName: newValue.name,
city: newValue.city,
country: newValue.country,
address: newValue.registrationAddress,
zipCode: newValue.zipCode
},
basketItems: [
{
id: newValue.productid,
name: newValue.productname,
category1: newValue.category1,
itemType: Iyzipay.BASKET_ITEM_TYPE.PHYSICAL,
price: newValue.price
},
]
}
iyzipay.payment.create(request, function (err, result) {
console.log(err, result);
const docRef1 = db.collection('results').doc().set(result);
})
})
Upvotes: 2
Views: 709
Reputation: 83191
You need to terminate a Cloud Function when all the asynchronous work is completed, see the doc. In the case of a background triggered Cloud Function (e.g. Cloud Firestore function onCreate
trigger, like your Cloud Function) you must return the chain of Promises returned by the asynchronous method calls.
I don't know the Iyzipay service nor the corresponding Node.js library, but it seems that there is no "promisified" version of the iyzipay.payment.create
method. You should therefore wrap it in a Promise and chain this Promise with the Promise returned by the Firestore asynchronous set()
method, as follows (untested).
exports.pay = functions
.region('europe-west1')
.firestore
.document('requests/{docId}')
.onCreate((snap, context) => {
const newValue = snap.data();
const request = { ... };
return new Promise(function (resolve, reject) {
iyzipay.payment.create(request, function (err, result) {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
.then(result => {
return db.collection('results').doc().set(result);
})
.catch(error => {
console.log(error);
return null;
});
});
If you want to write something to the log when the operation is completed, do as follows:
// ...
.onCreate((snap, context) => {
const newValue = snap.data();
const request = { ... };
return new Promise(function (resolve, reject) {
// ...
})
.then(result => {
return db.collection('results').doc().set(result);
})
.then(() => {
console.log("Operation completed: " + result);
return null;
})
.catch(error => {
console.log(error);
return null;
});
How can I add another Firestore query after
return db.collection('results').doc().set(result);
? For example, I want to update a field in a document, so where can I adddb.collection('listings').doc(newValue.productid).update({sold : true})
?
You have two possibilities:
Since the update()
method is an asynchronous method which returns a Promise (like all Firebase asynchronous methods), you need to add it to the chain of promises, as follows:
return new Promise(function (resolve, reject) {
iyzipay.payment.create(request, function (err, result) {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
.then(result => {
return db.collection('results').doc().set(result);
})
.then(() => {
return db.collection('listings').doc(newValue.productid).update({sold: true});
})
.catch(error => {
console.log(error);
return null;
});
Since both the set()
the update()
methods write to a document, you could use a batched write, as follows:
return new Promise(function (resolve, reject) {
iyzipay.payment.create(request, function (err, result) {
if (err) {
reject(err)
} else {
resolve(result)
}
})
})
.then(result => {
const batch = db.batch();
var docRef1 = db.collection('results').doc();
batch.set(docRef1, result);
var docRef2 = db.collection('listings').doc(newValue.productid);
batch.update(docRef2, {sold: true});
return batch.commit();
})
.catch(error => {
console.log(error);
return null;
});
The difference with Approach #1 is that the two writes are done in one atomic action.
PS: Note that instead of doing db.collection('results').doc().set(result);
you could do db.collection('results').add(result);
Upvotes: 4