Exception Handler Framework - Web Page Example

A reusable framework for handling exceptions within Node-RED flows.

You can find a complete description of the framework here: https://developer.ibm.com/recipes/tutorials/nodered-exception-handling-framework/

[{"id":"ecfdd6a6.897898","type":"tab","label":"Exception - Web Page"},{"id":"178f00e9.70cf9f","type":"template","z":"ecfdd6a6.897898","name":"getDivisorPage","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        <!--  <link rel=\"stylesheet\" href=\"https://www.w3schools.com/w3css/3/w3.css\">  -->\n\t    <title>Exception Handler Test Harness</title>\n\n        <script>\n            function updateBox() {\n                //Clear text box.\n                element1 = document.getElementById('txtBox1');\n                if(element1 != null) {\n                    document.getElementById('txtBox1').value = \"\";\n                }\n                //Clear Text Area. \n                document.getElementById(\"reslt\").value = \"\";\n                \n            }\n\n            function updateRB() {\n                //Clear radio buttons.\n                element1 = document.getElementById('rb1');\n                if(element1 != null) {\n                    document.getElementById('rb1').checked = false;\n                }\n    \n                element1 = document.getElementById('rb2');\n                if(element1 != null) {\n                    document.getElementById('rb2').checked = false;\n                }\n                //Clear text box.\n                document.getElementById(\"reslt\").value = \"\";\n            }\n        </script>\n    </head>\n\n    <body\">   \n\n        <form id=\"xcpForm\" method=\"POST\" action=\"/compute\">\n            <p>Enter a numeric value for the divisor:</p>\n            <input type=\"number\" name=\"numDivisor\" onClick=\"updateRB();\" min=\"-2\" max=\"2\" id=\"txtBox1\" value=\"\">\n            <p>Or select an option below:</p>\n            <p></p>\n            <input type=\"radio\" name=\"nanDivisor\" onClick=\"updateBox();\" id=\"rb1\" value=$> Non-numeric Divisor<br>\n            <input type=\"radio\" name=\"nanDivisor\" onClick=\"updateBox();\" id=\"rb2\" value=\"\"> Empty Divisor<br> \n            <p></p>\n            <input type=\"submit\" value=\"Submit\">\n            <p></p>\n            <p></p>\n        </form>\n        <p>The result:</p>\n        <textarea id=\"reslt\" readonly> {{current.result}} </textarea>\n\n    </body>\n</html>","x":525,"y":234,"wires":[["1f6c5ab9.a1367d"]]},{"id":"cda76753.95af88","type":"http in","z":"ecfdd6a6.897898","name":"","url":"/input","method":"get","swaggerDoc":"","x":85,"y":235,"wires":[["178f00e9.70cf9f"]]},{"id":"1f6c5ab9.a1367d","type":"http response","z":"ecfdd6a6.897898","name":"","x":683,"y":234,"wires":[]},{"id":"e46ad957.c950f8","type":"function","z":"ecfdd6a6.897898","name":"computeFraction","func":"//Add a trace to a function with node.log\n//node.log(\"Entering the computeFraction node\");\n\n//  Compute Fraction\nfunction computeFraction(divisor) {\n    var fncResult = 1 / divisor;\n    return (fncResult);\n}\n\n// Evaluate payload passed from Web page.\nvar divisorValue = null;\nif (msg.payload.nanDivisor) {\n    divisorValue = msg.payload.nanDivisor;\n} else {\n    divisorValue = msg.payload.nanDivisor;\n}\n\nif (msg.payload.numDivisor) {\n    divisorValue = Number(msg.payload.numDivisor);\n}\n\n//Evaluate Divisor and determine if we will compute the fraction.\ntry {\n\tif (typeof(divisorValue) === 'number') {\n\t\tif (divisorValue === 0) {\n\t\t\tthrow \"isZero\";\n\t\t} else {\n\t\t\tmsg.fraction = computeFraction(divisorValue);\n\t\t}\n\t} \n\telse {\t\t// Parameter is not numeric.\n\t\tif (divisorValue === \"\") {\t\n\t\t\tthrow \"isEmpty\";\n\t\t} else {\t// Divisor is some other non-numeric value.\n        \t\tthrow \"isNaN\";       \n    \t\t}\n\t}\n}\n\ncatch (e) {\n    \n    //  Raise error and terminate processing.  This will invoke custom error handler.\n    node.error(e, msg);\n    return; \n\n} finally {\n\t//executes whether or not an exception is thrown.\n}\n\nreturn msg;","outputs":"1","noerr":0,"x":305,"y":283,"wires":[["885f95f9.44e3"]]},{"id":"a14932d2.c7114","type":"http in","z":"ecfdd6a6.897898","name":"","url":"/compute","method":"post","swaggerDoc":"","x":107,"y":283,"wires":[["e46ad957.c950f8"]]},{"id":"6da562cb.838d24","type":"comment","z":"ecfdd6a6.897898","name":"Web Page Test Harness","info":"","x":125,"y":185,"wires":[]},{"id":"eab0e82c.5443f8","type":"function","z":"ecfdd6a6.897898","name":"Exception Message Lookup","func":"//Obtain the Application ID.\nvar xcpAppID = flow.get(\"appID\");\nif (!xcpAppID) {\n    msg.exceptionMsg = \"Application error. Application identifier doesn't exist.\";\n    msg.nextAction = 999;\n    return msg;\n}\n\n//Obtain exception message from Throw.\nvar xcpLabel = msg.error.message;\nif (!xcpLabel) {\n    msg.exceptionMsg = \"Application error. Exception lable doesn't exist.\";\n    msg.nextAction = 999;\n    return msg;\n}\n\n//Determin existence of JSON containing exception messages.\nvar xcpMsgObj = global.get(\"xMsgObj\");\nif (!xcpMsgObj) {\n    msg.exceptionMsg = \"Application error. Exception JSON doesn't exist.\";\n    msg.nextAction = 999;\n    return msg;\n}\n\n//Obtain Current Timestamp in ISO format.\nfunction getCurrentTS() {\n    var dtGMT = new Date();\n    var strGMT = dtGMT.toISOString(); \n\n    return strGMT;\n}\n\n//Retrieve exception message by Application ID and exception label.\nfunction getByKey(xcpAppID, xcpLabel, currentTS, xcpMsgObj) {\n     var rslt = {\n        exceptionMsg: null,\n        nextStep: null\n        };\n\n    for (var i = 0; i < xcpMsgObj.length; i++) {\n        var xcpRecord = xcpMsgObj[i];\n        if ((xcpRecord.applicationID == xcpAppID) && \n        (xcpRecord.exceptionLabel == xcpLabel) && \n        (xcpRecord.activationTS <= currentTS) &&\n        (xcpRecord.deactivationTS >= currentTS)\n        ) {\n            rslt.exceptionMsg = xcpRecord.exceptionMsg;\n            rslt.nextStep = xcpRecord.nextStep;\n        }\n    }\n    return rslt;\n}\n\n//Retrieve Current Timestamp.\nvar currentTS = getCurrentTS();\n\n//Call the message lookup function to obtain exception message.\nvar xcpResult = getByKey(xcpAppID, xcpLabel, currentTS, xcpMsgObj);\nif (xcpResult) {\n    msg.exceptionMessage = xcpResult.exceptionMsg;\n    msg.nextStep = xcpResult.nextStep;\n} else {\n    msg.exceptionMessage = \"Application error. Exception message not found in data store.\";\n    msg.nextStep = 999;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":402,"y":489,"wires":[["e8ae9800.bba678"]]},{"id":"19dc019f.a545ae","type":"catch","z":"ecfdd6a6.897898","name":"Exception Handler Catch","scope":["e46ad957.c950f8"],"x":119.5,"y":489,"wires":[["eab0e82c.5443f8"]]},{"id":"829d0d59.258368","type":"comment","z":"ecfdd6a6.897898","name":"Exception Handler","info":"","x":112,"y":440,"wires":[]},{"id":"4035a0bb.2b2fc","type":"debug","z":"ecfdd6a6.897898","name":"Test Harness Output","active":true,"console":"false","complete":"true","x":811.5,"y":70.25,"wires":[]},{"id":"b07b14b3.82fe18","type":"function","z":"ecfdd6a6.897898","name":"Set Application ID in Flow Context","func":"//Set the Application ID to be used to retrieve an exception message.\nflow.set(\"appID\", msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":412,"y":69.5,"wires":[["4035a0bb.2b2fc"]]},{"id":"d40ec2ac.f8d22","type":"function","z":"ecfdd6a6.897898","name":"Set Exception Object in Global Context","func":"//Set the message object exception messages will be retrieved from.\nglobal.set(\"xMsgObj\", msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":503,"y":107.25,"wires":[["4035a0bb.2b2fc"]]},{"id":"bc777ba5.55954","type":"inject","z":"ecfdd6a6.897898","name":"Set Application ID","topic":"","payload":"2","payloadType":"str","repeat":"","crontab":"","once":true,"x":153,"y":70,"wires":[["b07b14b3.82fe18"]]},{"id":"8cb9840b.7428c","type":"inject","z":"ecfdd6a6.897898","name":"Set Exception Msg Object","topic":"","payload":"[{ \t\"applicationID\": \"1\", \t\"exceptionLabel\": \"isNaN\", \t\"exceptionMsg\": \"The divisor entered is not numeric.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": null }, { \t\"applicationID\": \"1\", \t\"exceptionLabel\": \"isZero\", \t\"exceptionMsg\": \"This message will be replaced during an alternate exception process.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": 55 }, { \t\"applicationID\": \"1\", \t\"exceptionLabel\": \"isEmpty\", \t\"exceptionMsg\": \"The divisor is empty.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": null }, { \t\"applicationID\": \"2\", \t\"exceptionLabel\": \"isNaN\", \t\"exceptionMsg\": \"The divisor entered is not numeric. Please retry.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": null }, { \t\"applicationID\": \"2\", \t\"exceptionLabel\": \"isZero\", \t\"exceptionMsg\": \"This message will be replaced during an alternate exception process.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": 55 }, { \t\"applicationID\": \"2\", \t\"exceptionLabel\": \"isEmpty\", \t\"exceptionMsg\": \"The divisor is empty. Please retry.\", \t\"activationTS\": \"2017-05-23T18:30:30.000Z\", \t\"deactivationTS\": \"2019-05-23T18:30:30.000Z\", \t\"nextStep\": null }]","payloadType":"json","repeat":"","crontab":"","once":true,"x":183.75,"y":107.75001525878906,"wires":[["d40ec2ac.f8d22"]]},{"id":"21105b6e.4827bc","type":"comment","z":"ecfdd6a6.897898","name":"Set Application ID and Load JSON Message Object","info":"","x":206,"y":20,"wires":[]},{"id":"885f95f9.44e3","type":"function","z":"ecfdd6a6.897898","name":"Map Result to Message","func":"//Determine if there is an exception. If not set result to computed fraction.\n\nif (msg.exceptionMessage) {\n    msg.current = {result: msg.exceptionMessage};\n} else {\n    msg.current = {result: msg.fraction};\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":602,"y":314,"wires":[["178f00e9.70cf9f"]]},{"id":"ca8254cb.c5ca","type":"debug","z":"ecfdd6a6.897898","name":"Uncaught Exceptions from Other Nodes","active":true,"console":"true","complete":"true","x":554.5,"y":603,"wires":[]},{"id":"2f49552d.088da2","type":"catch","z":"ecfdd6a6.897898","name":"Catch for all other nodes","scope":["178f00e9.70cf9f","885f95f9.44e3","b07b14b3.82fe18","d40ec2ac.f8d22"],"x":115,"y":602.75,"wires":[["ca8254cb.c5ca"]]},{"id":"1f1176c.b0d1589","type":"debug","z":"ecfdd6a6.897898","name":"Exceptions from Message Lookup","active":true,"console":"true","complete":"true","x":535,"y":543,"wires":[]},{"id":"1c63200d.37f2f8","type":"catch","z":"ecfdd6a6.897898","name":"Catch for Exception Message Lookup","scope":["eab0e82c.5443f8"],"x":155,"y":543.75,"wires":[["1f1176c.b0d1589"]]},{"id":"e8ae9800.bba678","type":"switch","z":"ecfdd6a6.897898","name":"Next Step Action","property":"nextStep","propertyType":"msg","rules":[{"t":"eq","v":"55","vt":"num"},{"t":"else"}],"checkall":"true","outputs":2,"x":662.5,"y":488.75,"wires":[["174c38f9.916f37"],["885f95f9.44e3"]]},{"id":"174c38f9.916f37","type":"function","z":"ecfdd6a6.897898","name":"Alternate Flow","func":"var inpMsg = \"The divisor entered (Zero) will cause a divide by zero error. Refer to: https://en.wikipedia.org/wiki/Division_by_zero\";\n\nmsg.exceptionMessage = inpMsg;\n\nreturn msg;","outputs":1,"noerr":0,"x":791.5,"y":405.75,"wires":[["885f95f9.44e3"]]}]
Behr3rd

Flow Info

created 2 months, 3 weeks ago

Node Types

Core
  • catch (x3)
  • comment (x3)
  • debug (x3)
  • function (x6)
  • http in (x2)
  • http response (x1)
  • inject (x2)
  • switch (x1)
  • template (x1)
Other
  • tab (x1)

Tags

  • exception
  • exceptionhandler
  • error
  • errorhandler
  • framework
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option