Conceito

Para desenvolver em Javascript, naturalmente é necessário conhecer alguma forma de lidar com chamadas assíncronas. Independente do jeito que faça, o problema tem que ser resolvido, mesmo que não fique tão bonito de ser visto ou escrito. Todavia, código limpo é uma ajuda à documentação do seu script.

Callbacks

function sayMyName (firstName, lastName, callback) {
  const jessy = "Jessy";
  console.log(`${firstName} ${lastName}`);
  callback(jessy);
}

sayMyName("Walter", "White", function (person) { // Callbacks podem ser executadas direto na chamada ou passadas como valor
  console.log(`You're goddamn right, ${person}`)
});

// Walter White
// You're goddamn right, Jessy

Inclusive, eventos utilizam este padrão. Veja como uma chamada XHR é executada:

function reqListener () {
  console.log(this.responseText);
}

var xhr = new XMLHttpRequest();
xhr.addEventListener("load", reqListener); // Executa o callback assim que o evento é chamado
xhr.open("GET", "http://www.example.org/example.txt");
xhr.send();

Promises

Com o es6, promises foram incluídas na linguagem após anos de uso de maneira não oficialziada nas bibliotecas open source. Com a sua chegada, chamadas foram simplificadas na escrita e na leitura. Assim que uma promise é executada, ela precisa ser resolvida ou rejeitada. Caso contrário, ficará em modo de espera, sem utilidade. No exemplo abaixo, utilizei o sequelize em Node para gerar um histórico de pedidos.

exports.ordersHistory = (rows, count) => {
  const orders = rows.map( order => {
    return order.getProducts()
    .then( products => Object.assign({}, order.toJSON(), { product: products.find( i => i) }));
  }); // Contém um array de promises

  return Promise.all(orders) // Aqui contém um array de pedidos já resolvidos
  .then( returnedOrders => {

    const theOrders = returnedOrders.map( ord => {
      return OrderStatus.findById(ord.orderStatusId)
      .then( orderStatus => {
        const query = {
          where: { ProductId: ord.product.id }
        };
        return ProductUrl.findAll(query)
        .then( images => {
          const product = ord.product.toJSON();
          product.images = images.map( i => i.toJSON());
          return Object.assign({}, ord, { orderStatus: orderStatus.toJSON() }, { product });
        }); // product
      }); // orderStatus
    }); // ord
    return Promise.all(theOrders);
  });
}

Async/Await

Mesmo as promises facilitando e simplificando bastante os callbacks, alguns desenvolvedores preferem a ideia do Async/Await, que nada mais é uma forma de escrever as promises mais estrutural e sem a eternidade de thenables (Cadeia de .then(…).then(…)). Enquanto fazia um servidor, descobri a biblioteca asyncawait, que possibilita a execução destes mesmo sem o ES7 e escrito com o JS existente. Veja uma função de pedido resumido com o uso.

...
doPayment: req => {
  const { body } = req;
  const { productId, UserId, productQuantity, mercadoPago, shippingType } = body;
  const { doPayment } = require('../MercadoPago/mercadoPago.model');

  const shippingAmount = Number(body.shippingAmount.includes(',') ? body.shippingAmount.replace(',', '.') : body.shippingAmount);
  const shippingDays = Number(body.shippingDays);

  return startTransaction( async ( transaction => {

    let response;
    try {
      const user = await (sequelize.models.User.findById(UserId, { transaction }));

      const addresses = await (user.getAddresses());
      if (addresses.length !== 2 ) throw 'Complete o seu cadastro!';

      const product = await (Product.findById(productId, { transaction }));

      const canBuy = await (user.canBuyProduct(product, transaction));

      if (!canBuy) throw 'Você já está em processo de pagamento. Aguarde até 10 minutos e tente novamente.';

      const currentProduct = await (Product.getCurrentProductWithoutStatus(transaction));

      console.log(`Product Quantity -> ${productQuantity}`);
      console.log(`Product -> ${product.id} | currentProduct -> ${currentProduct.id}`);

      if (productQuantity < 0 || productQuantity === undefined) throw 'Quantidade de produtos inválido!';
      if (productQuantity > currentProduct.purchaseLimit) throw 'Quantidade selecionada maior do que o limite por usuário';
      if (_.isEmpty(currentProduct) || product.id !== currentProduct.id) throw 'Parece que este produto não está mais ativo';
      if (currentProduct.stockQty <= 0) {
        const isAbleToQueue = await (user.isAbleToQueue(currentProduct, transaction));

        if (!isAbleToQueue) throw 'Todos os produtos foram vendidos. Você já está na fila de espera, aguarde para ser notificado caso haja desistência (:';
        else throw 'sendNotification';
      }

      response = await (doPayment(mercadoPago));
      const status = response.status;
      const billingTrackingNumber = response.id;

      const userStatistics = await (sequelize.models.UserStatistics.getByUser(user, transaction));

      const analytics = await (sequelize.models.Analytics.getTodaysAnalytics(transaction));

      //passou nas validações -> criar order
      const newOrder = Object.assign(
        {},
        _.omit(body, ['productQuantity', 'productId', 'mercadoPago', ]),
        { totalAmount: parseFloat((currentProduct.discountedPrice * productQuantity) + shippingAmount) },
        { paymentTime: new Date() }, { inQueue: false }, { billingTrackingNumber }
      );

      const order = await (Order.create(newOrder, { transaction }));

      const orderItem = {
        OrderId: order.id,
        ProductId: product.id,
        productQuantity: productQuantity
      };

      const createdOrderItem = await (OrderItem.create(orderItem, { transaction }));

      const orderStatus = await (OrderStatus.getByDescription(status, { transaction }));

      await (order.setOrderStatus(orderStatus.id, { transaction }));

      if (status !== 'rejected') {
        // Decrement no estoque
        await (currentProduct.decrement('stockQty', { transaction, by: productQuantity }));
        // Incrementando o total de compras
        await (currentProduct.increment('totalBought', { transaction, by: productQuantity }));
        // Incrementando o total economizado pelo user
        await (user.increment('totalSaved', { transaction, by: Number(currentProduct.storePrice - currentProduct.discountedPrice)}));
        // Incrementando o total compartilhado pelo user
        await (user.increment('totalBought', { transaction, by: productQuantity }));
        // Incrementando o total de compras do user
        await (userStatistics.increment('totalBought', { transaction, by: productQuantity }));
        // Incrementando o total de compras no total
        await (analytics.increment('totalBought', { transaction, by: productQuantity }));
        await (analytics.increment('totalAmount', { transaction, by: order.totalAmount }));
      }

    } catch (err) {
      console.log(err);
      throw err;
    }
    // Se o response vier com sucesso, commit no transaction, senão 'throw err';
    return response;
  }));
}
...

É apenas necessário colocar o escopo atual dentro do async e então as funções que retornam promises dentro do await para deixar o código um pouco mais legível.

Lembre-se que com o Babel dá para fazer isto sem a ajuda de bibliotecas. Veja

Referências

Novamente, o site developer.mozilla é sempre uma boa fonte para pesquisa, assim como o blog do grande Willian, que é um dos maiores (se não o maior) de frontend do Brasil.