超时后从完成的函数中忽略异常

时间:2019-05-03 15:14:23

标签: node.js google-cloud-platform google-cloud-functions

我正在研究解析大量数据的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');
  },
);

0 个答案:

没有答案