我正在研究解析大量数据的HTTP云功能。此功能花费的时间超过60000毫秒,因此我得到了Function execution took 60002 ms, finished with status: 'timeout'
,这是预期的,但是该功能继续起作用。
当函数完成(超时)后不久出现异常时,就会出现问题。它正在抛出Ignoring exception from a finished function
,所以我无法调试问题,因为我不知道幕后发生了什么。
我应该怎么处理?
import { region, config, auth as authTypes } from 'firebase-functions';
import { request } from 'https';
import to from 'await-to-js';
import { db, auth } from './app';
import { Investment } from './types/Investment';
import { InvestorRecord, InvestorTier, InvestorStatus } from './types/InvestorRecord';
import { Asset } from './types/Asset';
import { PaymentStatus } from './types/Payment';
const CONFIG = config();
exports = module.exports = region('europe-west1').https.onRequest(
async (mainRequest, mainResponse): Promise<void> => {
const { dealStageID, fundName } = mainRequest.query;
if (!dealStageID || !fundName) {
throw new Error('Error with params');
}
// Function that handles a request to meerdervoort's sharpspring API
// It returns a fully parsed JSON response
const meerdervoortDatabaseRequest = (data): Promise<{ [key: string ]: any }> => new Promise((resolve, reject): void => {
const stringifiedData = JSON.stringify(data);
const options = {
hostname: 'api.sharpspring.com',
port: 443,
path: `/pubapi/v1/?accountID=${CONFIG.sharp_spring.account}&secretKey=${CONFIG.sharp_spring.key}`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': stringifiedData.length,
},
};
// Opportunity Leads request
const mRequest = request(options, (meerderResponse): void => {
const bufferedData = [];
meerderResponse.on('data', (dataBuffer): void => {
bufferedData.push(dataBuffer);
});
meerderResponse.on('end', (): void => {
resolve(JSON.parse(Buffer.concat(bufferedData).toString()));
});
});
mRequest.on('error', (error): void => {
reject(error);
});
mRequest.write(stringifiedData);
mRequest.end();
});
const fund = {
dealStageID: 399799298,
name: 'MGF II',
};
const [speOppoError, speOppoSuccess] = await to(meerdervoortDatabaseRequest({
method: 'getOpportunities',
params: {
where: {
dealStageID,
},
},
id: 1,
}));
if (speOppoError) {
mainResponse.status(500).send(speOppoError.message);
return;
}
const opportunities = (speOppoSuccess.result.opportunity as any[]).filter((opp): boolean => opp.isWon === '1');
// Using custom asyncForEach because a promise.all with transactions makes too many collisions and Firebase doesn't support more than 5
const asyncForEach = async (array, callback): Promise<any> => {
for (let index = 0; index < array.length; index++) {
try {
await callback(array[index], index, array);
} catch (e) {
console.log('unfinished');
mainResponse.status(500).send(e);
throw new Error(e);
}
}
};
const leads = { };
await asyncForEach(opportunities, async (oppo, i): Promise<void> => {
console.log(`Loading... ${Math.round(i / opportunities.length * 100)} %`);
// Here we are copying the pointer, not the data
let user = leads[oppo.originatingLeadID || oppo.primaryLeadID];
/** USER / INVESTOR PART */
let investorId = '';
// If the user (lead) has not been tracked down yet, we do it now
if (!user || Object.keys(user).length === 0) {
const [getLeadsError, getLeadsSuccess] = await to(meerdervoortDatabaseRequest({
method: 'getLead',
params: {
// originatingLeadID can be '0' so we convert to Number and won't give problems with the OR condition
id: Number(oppo.originatingLeadID) || Number(oppo.primaryLeadID),
},
id: i,
}));
if (getLeadsError) {
throw new Error(getLeadsError.message);
}
const lead = getLeadsSuccess.result.lead[0];
if (lead.emailAddress) {
// console.log(lead.emailAddress);
// Check if entry is in Firebase Auth
const [getUserError, getUserSuccess] = await to(auth.getUserByEmail(lead.emailAddress));
if (getUserError && getUserError.code !== 'auth/user-not-found') {
throw new Error(getUserError.message);
}
// If not, register in Firebase Auth
if (!getUserSuccess) {
// Code to register
const [registerUserError, registerUserSuccess] = await to(auth.createUser({
disabled: false,
email: lead.emailAddress,
emailVerified: false,
}));
if (registerUserError && registerUserError.code !== 'auth/invalid-email') {
throw new Error(registerUserError.message);
}
if (!registerUserError) {
user = { ...registerUserSuccess };
}
} else {
// Updating content inside the pointer
user = { ...getUserSuccess };
}
}
const extraData = (investor): { [key: string]: any } => {
const tempInvestor: { [key: string]: any } = { ...investor };
if (lead.firstName) {
tempInvestor.name = lead.firstName;
}
if (lead.lastName) {
tempInvestor.surName = lead.lastName;
}
if (lead.street) {
tempInvestor.streetAddress = lead.street;
}
if (lead.zipcode) {
tempInvestor.postalCode = (lead.zipcode as string).replace(/\s/g, '');
}
if (lead.mobilePhoneNumber || lead.phoneNumber) {
tempInvestor.phone = lead.mobilePhoneNumber || lead.phoneNumber;
}
['city', 'country'].forEach((key): void => {
if (lead[key]) {
tempInvestor[key] = lead[key];
}
});
return tempInvestor;
};
// Check if entry is in Firestore as Investor
if (user && (user as authTypes.UserRecord).uid) {
const [getInvestorError, getInvestorSuccess] = await to(db.collection('investors').doc((user as authTypes.UserRecord).uid).get());
if (getInvestorError) {
throw new Error(getInvestorError.message);
}
const investorData = getInvestorSuccess.data();
if ((getInvestorSuccess.exists && investorData && !investorData.identified) || !getInvestorSuccess.exists) {
let investor: { [key: string]: any } = {
updatedDatetime: Date.now(),
identified: true,
};
investor = extraData(investor);
const [updateIdentifiedError] = await to(db.collection('investors').doc((user as authTypes.UserRecord).uid).set(investor, { merge: true }));
if (updateIdentifiedError) {
throw new Error(updateIdentifiedError.message);
}
// Updating content inside the pointer
user = { ...user, ...investorData };
}
investorId = getInvestorSuccess.id;
} else {
const [getInvestorError, getInvestorSuccess] = await to(
db.collection('investors').where('name', '==', lead.firstName).where('streetAddress', '==', lead.street).get(),
);
if (getInvestorError) {
throw new Error(getInvestorError.message);
}
if (getInvestorSuccess.empty) {
const [getLastInvestorError, investorsQueryResult] = await to(db.collection('investors').orderBy('customId', 'desc').limit(1).get());
if (getLastInvestorError) {
throw new Error('There was an error reading investors data.');
}
const lastId = investorsQueryResult.size === 1 ? investorsQueryResult.docs[0].data().customId : 0;
let investor: { [key: string]: any } = {
name: lead.firstName,
surName: lead.lastName,
tier: InvestorTier.Starter,
createdDatetime: Date.now(),
updatedDatetime: Date.now(),
status: InvestorStatus.Enabled,
identified: true,
customId: lastId + 1,
};
investor = extraData(investor);
const investorRef = db.collection('investors').doc();
const [registerInvestorError] = await to(investorRef.create(investor));
if (registerInvestorError) {
throw new Error(registerInvestorError.message);
}
user = { ...investor, uid: 'NOT REGISTERED' };
investorId = investorRef.id;
} else {
user = { ...getInvestorSuccess.docs[0].data() };
investorId = getInvestorSuccess.docs[0].id;
}
}
}
/** INVESTMENT / PAYMENT PART */
const [getAssetError, getAssetSuccess] = await to(
db.collection('assets').where('name', '==', fundName).get(),
);
if (getAssetError) {
throw new Error(getAssetError.message);
}
if (getAssetSuccess.empty) {
throw new Error('Asset not found.');
}
const asset = getAssetSuccess.docs[0].data() as Asset;
const assetId = getAssetSuccess.docs[0].id;
const [getInvestmentError, getInvestmentSuccess] = await to(
db.collection('investments').where('assetId', '==', assetId).where('investorId', '==', investorId).get(),
);
if (getInvestmentError) {
throw new Error(getInvestmentError.message);
}
const date = Date.now();
const investment: Investment = {
assetId,
createdDatetime: date,
updatedDatetime: date,
userId: user.uid || 'NOT REGISTERED',
payments: [],
investorId,
};
const paidEuro = Number(oppo.amount);
const sharesBought = paidEuro / asset.sharePrice;
if (getInvestmentSuccess.empty) {
investment.paidEuroTotal = Number(oppo.amount);
investment.boughtSharesTotal = sharesBought;
} else {
const foundInvestment = getInvestmentSuccess.docs[0].data() as Investment;
investment.createdDatetime = foundInvestment.createdDatetime;
investment.payments = foundInvestment.payments;
investment.paidEuroTotal = foundInvestment.paidEuroTotal + paidEuro;
investment.boughtSharesTotal = foundInvestment.boughtSharesTotal + sharesBought;
}
// Get from example: looptijd_mgf_ii__1__5c34a9b697a02 that full keyname (cannot be null)
const findKey = Object.keys(oppo).find((key): boolean => key.startsWith('looptijd_') && oppo[key]);
// Get from example: '7 jaar' the first number/combination of strings
let dividendsFormatYears = '';
try {
dividendsFormatYears = (oppo[findKey] as string).substr(0, (oppo[findKey] as string).indexOf(' '));
} catch (e) {
console.log(findKey);
console.log(oppo);
throw new Error('Error at parsing string');
}
try {
investment.payments = [
...investment.payments,
{
createdDatetime: date,
id: oppo.id,
provider: 'Transfer',
updatedDatetime: date,
userId: user.uid || 'NOT REGISTERED',
investmentId: getInvestmentSuccess.empty ? 'unknown' : getInvestmentSuccess.docs[0].id, // ToDo: fix unknown
dividendsFormat: asset.dividendsFormat.find((contentsObject): boolean => contentsObject.contents[0] === dividendsFormatYears).contents,
providerData: {
id: oppo.id,
amount: {
currency: 'EUR',
value: oppo.amount,
},
status: PaymentStatus.Paid,
},
},
];
} catch (e) {
throw new Error(e);
}
// Create/update investment
const ref = getInvestmentSuccess.empty
? db.collection('investments').doc()
: db.collection('investments').doc(getInvestmentSuccess.docs[0].id);
const [setInvesmentError] = await to(ref.set(investment, { merge: true }));
if (setInvesmentError) {
throw new Error(setInvesmentError.message);
}
// Update Asset
const [updateAssetError] = await to(db.collection('assets').doc(assetId).update({
sharesAvailable: asset.sharesAvailable - sharesBought,
}));
if (updateAssetError) {
throw new Error(updateAssetError.message);
}
});
console.log('finished');
// @ts-ignore
mainResponse.status(200).send('OK');
},
);