Even with async/await, raw promises are still key to writing optimal concurrent javascript
Daniel Brain

I think you just need a utility that handles workflows for you, a la caolan/async’s auto function:

async function makePizza(sauceType = 'red') {
const { dough, sauce, cheese } = await auto({
dough: makeDough,
sauce: async () => makeSauce(sauceType)
cheese: [
async ({sauce}) => grateCheese(sauce.determineCheese())

return dough;

Here, dependencies are explicit, and the flow is tersely described, while each step can be safely abstracted away. In order to fully meet the API presented by async you’d need to pass the workflow results in any errors handled, but that’s relatively trivial.