Here is an example - I'm using the exec to call say on my mac as that exhibits the same behaviour... ie if triggered multiple times it will overlap the spoken words... using the flow below it waits for the exec to finish before releasing the next message.
[{"id":"c503216.f3afce","type":"inject","z":"9e538f88.61ac7","name":"","topic":"","payload":"\"Isn't it nice to have a computer that will talk to you?\"","payloadType":"string","repeat":"","crontab":"","once":false,"x":149,"y":583,"wires":[["8d68ad.ff72975"]]},{"id":"8d68ad.ff72975","type":"function","z":"9e538f88.61ac7","name":"Simple triggered queue","func":"// if queue doesn't exist, create it\ncontext.queue = context.queue || [];\ncontext.busy = context.busy || false;\n\n// if the msg is a trigger one release next message\nif (msg.hasOwnProperty(\"trigger\")) {\n if (context.queue.length > 0) {\n var m = context.queue.shift();\n return {payload:m};\n }\n else {\n context.busy = false;\n }\n}\nelse {\n if (context.busy) {\n // if busy add to queue\n context.queue.push(msg.payload);\n }\n else {\n // otherwise we are empty so just pass through and set busy flag\n context.busy = true;\n return msg;\n }\n}\n\nreturn null;","outputs":1,"noerr":0,"x":374,"y":586,"wires":[["8bb0eecc.744f1"]]},{"id":"e872b22f.178d5","type":"function","z":"9e538f88.61ac7","name":"set trigger","func":"// handle the return from the exec in here \n// if all is good then set msg.trigger property to exist\nmsg.trigger = 1;\nreturn msg;","outputs":1,"noerr":0,"x":654,"y":740,"wires":[["8d68ad.ff72975"]]},{"id":"8bb0eecc.744f1","type":"exec","z":"9e538f88.61ac7","command":"say","addpay":true,"append":"","useSpawn":false,"name":"","x":589,"y":587,"wires":[["23263896.dcd9c8","e872b22f.178d5"],[],[]]},{"id":"23263896.dcd9c8","type":"debug","z":"9e538f88.61ac7","name":"","active":true,"console":"false","complete":"false","x":815,"y":576,"wires":[]}]