如何强制Promise抛出指定自定义错误并传递至Promise.all的catch?
Got it, let's break down why your Promise.all isn't picking up those custom errors, and how to fix the writeAttachedCMinfo function properly.
The Core Problem
Right now, when you hit a failed condition (like CM amount > invoice amount), you're doing return new Error('CMisbiggerThanInvoice') — but this doesn't reject the parent Promise. Those return statements are inside nested then callbacks, not in the top-level executor function of your Promise. Returning an Error object here just passes it to the next then in the chain, not the catch handler of your outer Promise.
Step-by-Step Fixes
Let's rewrite the writeAttachedCMinfo function to properly reject on failed conditions, and clean up the Promise chaining to avoid nested callbacks (which makes error handling way easier):
server.writeAttachedCMinfo = function (invId, cmid) { return new Promise(function (resolve, reject) { console.log("writeAttachedCMinfo"); console.log("invoiceId " + invId); console.log("cmid " + cmid); // Clean up ID strings (simplified) const invIdString = JSON.stringify(invId).slice(1, -1); const cmIdString = JSON.stringify(cmid).slice(1, -1); const projection = 'gwCode certifiedInvoiceAmount buyerReference supplierReference invoiceNo invoiceSerialNo invoiceFiles creditMemos'; // Use chained Promises instead of nesting for clearer flow ubiqInvoice.findById(invIdString, projection) .then(function (dbInvoice) { const intInvCertifiedAmount = parseInt(dbInvoice.certifiedInvoiceAmount); // Return the next Promise to continue the chain return creditMemo.findById(cmIdString).then(function (dbCreditMemo) { return { dbInvoice, dbCreditMemo, intInvCertifiedAmount }; }); }) .then(function ({ dbInvoice, dbCreditMemo, intInvCertifiedAmount }) { const intCreditMemoAmount = parseInt(dbCreditMemo.creditMemoAmount); // Check conditions and REJECT explicitly if they fail if (intInvCertifiedAmount <= intCreditMemoAmount) { console.log('cm bigger than invoice'); reject(new Error('CMisbiggerThanInvoice')); return; // Exit early to avoid unnecessary execution } if (dbCreditMemo.isAssociated) { reject(new Error('CMisAssociated')); return; } const validGwCodes = ["100000000000", "110000000000", "111200000000", "111100000000", "111110000000"]; if (!validGwCodes.includes(dbInvoice.gwCode)) { reject(new Error('wrongggwcode')); return; } // If all checks pass, perform the database updates const creditMemoEntry = { id: guid.create().value, batchId: dbCreditMemo.batchId, invoiceId: dbInvoice._id, recordTypeCode: "CM", buyerReference: dbInvoice.buyerReference, supplierReference: dbInvoice.supplierReference, creditMemoNo: dbCreditMemo.creditMemoNo, creditMemoIssuingDate: dbCreditMemo.creditMemoIssuingDate, creditMemoEffectiveDate: dbCreditMemo.creditMemoEffectiveDate, lastModificationDate: dbCreditMemo.lastModificationDate, currencyCode: dbCreditMemo.currencyCode, creditMemoAmount: dbCreditMemo.creditMemoAmount, hashCode: dbCreditMemo.hashCode, description: dbCreditMemo.description, uploadDate: dbCreditMemo.uploadDate, isAssociated: true, }; dbInvoice.creditMemos.push(creditMemoEntry); dbInvoice.certifiedInvoiceAmount = dbInvoice.certifiedInvoiceAmount - dbCreditMemo.creditMemoAmount; // Wait for both saves to complete before resolving return Promise.all([dbInvoice.save(), dbCreditMemo.save()]) .then(() => resolve(dbInvoice)); }) // Catch all internal errors (queries, saves, etc.) and pass to outer Promise .catch(function (error) { console.log("error: " + error); reject(error); }); }); };
Key Changes Explained
- Explicit
reject()calls: Every failed condition now triggersreject(new Error('your-error-message'))— this forces the Promise into a rejected state, which will be picked up by yourPromise.allcatch handler. - Chained Promises: Replaced nested
thencallbacks with a linear chain, making error propagation consistent and the code easier to read. - Internal error handling: Added a top-level
catchin the Promise chain to catch any errors from database queries or saves, ensuring they don't get swallowed. - Wait for all saves: Used
Promise.allfor the two database save operations to ensure both complete successfully before resolving the outer Promise.
Your Updated Promise.all Call
Your existing Promise.all code works fine once the writeAttachedCMinfo function is fixed. You can tweak the catch handler to use the error message for cleaner output:
Promise.all(receivedObjs.arrayCMsIds.map(cmid => server.writeAttachedCMinfo(invId, cmid))) .then(function (results) { return res.json(apiHelp.success(results, "success")); }).catch(function (error) { res.json(apiHelp.error(error.message, error.message)); });
Final Notes
- Rule of thumb: In Promise-based code, returning an Error doesn't reject the Promise — you have to explicitly call
reject(). - Avoid deep nesting of
thencallbacks; chaining them keeps error handling predictable. - Always catch errors from all async operations (like database calls) to prevent silent failures.
内容的提问来源于stack exchange,提问作者user9601534

