Chaining promises into a callback I wrote a AWS lambda function which buys €X worth of some currency on an exchange. For this I first have to find out the current price (getAskPrice), then calculate the amount to buy and post the order (buyLimitOrder). I've implemented both operations as promises, buyLimitOrder gets the result of getAskPrice as one of the arguments. The result of buyLimitOrder (if successful) should be returned as the result of the lambda function.

Here's the relevant code fragment:

const getAskPrice = new Promise(function(resolve, reject){

        got(TICKER_URL).then(response =&gt; {
            let result = JSON.parse(response.body);
            let ask = Number(result.ask);
            if (!isNaN(ask)) {
                resolve(ask);
            } else {
                throw new Error("Could not parse the ask price from [" + BTCEUR_TICKER_URL + "] response:\n" + response.body);
            }
        }).catch(reject);
});

const buyLimitOrder = function(customerId, key, secret, value, askPrice) {

    let amount = value / askPrice;

    let roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);

    let order = createAuthData(customerId, key, secret);
    order.amount = roundedAmount;
    order.price = askPrice;
    return new Promise(function(resolve, reject) {
        console.log("Buying " + roundedAmount + " of BTC for price " + askPrice + " for total value of " + (roundedAmount * askPrice) + ".");
        got.post(BUY_LIMIT_ORDER_URL, {
            body : order,
            form: true
        }).then(response =&gt; {
            let result = JSON.parse(response.body);
            resolve(result);
        }).catch(reject);
    });
};

const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
    return new Promise(function(resolve, reject) {
        getAskPrice
            .then(askPrice =&gt; buyLimitOrder(customerId, key, secret, value, askPrice).then(resolve).catch(reject))
            .catch(reject);
    });
};

exports.handler = (event, context, callback) =&gt; {
    doWithBitstampProperties()
        .then(bitstampProperties =&gt; 
            getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
                .then(result =&gt; callback(null, result))
                .catch(error =&gt; callback(error)))
        .catch(error =&gt; callback(error));
};


Full code can be found here.

This works, but I was not sure this is how promises should be chained. For instance, calling .then(resolve).catch(reject) on buyLimitOrder(...) does not feel right:

    return new Promise(function(resolve, reject) {
        getAskPrice
            .then(askPrice =&gt; buyLimitOrder(customerId, key, secret, value, askPrice).then(resolve).catch(reject))
            .catch(reject);
    });


Generally, the code does not feel elegant.
Removing Promise constructor wrappers

As @Peilonrayz mentioned in his comment, we can return the promises directly instead of wrapping them in Promise constructors.

From the MDN: Promise docs:


  The constructor is primarily used to wrap functions that do not already support promises.


Since we are already returning promises (and not wrapping callbacks, for example) we can remove the constructors.

For example, doWithBitstampProperties:

const doWithBitstampProperties = function() {
    return new Promise(function(resolve, reject){
        if (BITSTAMP_PROPERTIES) {
            resolve(BITSTAMP_PROPERTIES);
        } else {
            decryptBitstampProperties().then(resolve).catch(reject);
        }
    });
};


Could be rewritten to return the promise directly:

const doWithBitstampProperties = function() {
  if (BITSTAMP_PROPERTIES) {
    return Promise.resolve(BITSTAMP_PROPERTIES);
  }

  return decryptBitstampProperties();
};


Or, using the conditional operator if you preferred to be more terse:

const doWithBitstampProperties = function() {
  return BITSTAMP_PROPERTIES
    ? Promise.resolve(BITSTAMP_PROPERTIES)
    : decryptBitstampProperties();
};


We can repeat this pattern for the other functions and reduce the noise that all the resolve/rejects added:

const AWS = require('aws-sdk');
const crypto = require('crypto');
const got = require('got');

const CURRENCY_PAIR = 'btceur';
const TICKER_URL = `https://www.bitstamp.net/api/v2/ticker/${CURRENCY_PAIR}/`;
const BUY_LIMIT_ORDER_URL = `https://www.bitstamp.net/api/v2/buy/${CURRENCY_PAIR}/`;

if (!AWS.config.region) {
    AWS.config.update({ region: 'eu-central-1' });
}

const ENCRYPTED_BITSTAMP_CUSTOMER_ID = process.env.BITSTAMP_CUSTOMER_ID;
const ENCRYPTED_BITSTAMP_KEY = process.env.BITSTAMP_KEY;
const ENCRYPTED_BITSTAMP_SECRET = process.env.BITSTAMP_SECRET;
const VALUE = Number(process.env.VALUE);
const MINIMUM_VALUE = 5;

if (isNaN(VALUE) || VALUE &lt; MINIMUM_VALUE) {
    throw new Error(`Invalid value [${value}], must be a number not less than [${MINIMUM_VALUE}].`);
}

const kms = new AWS.KMS();

const decrypt = function(encrypted) {
    return kms
        .decrypt({ CiphertextBlob: new Buffer(encrypted, 'base64') })
        .promise()
        .then(data =&gt; data.Plaintext.toString('ascii'))
};

let BITSTAMP_PROPERTIES;

const decryptBitstampProperties = function() {
    return Promise.all([
        decrypt(ENCRYPTED_BITSTAMP_CUSTOMER_ID),
        decrypt(ENCRYPTED_BITSTAMP_KEY),
        decrypt(ENCRYPTED_BITSTAMP_SECRET)
    ]).then(([customerId, key, secret]) =&gt;
        BITSTAMP_PROPERTIES = { customerId, key, secret }
    );
})};

const doWithBitstampProperties = function() {
    return BITSTAMP_PROPERTIES
        ? Promise.resolve(BITSTAMP_PROPERTIES)
        : decryptBitstampProperties();
};

const createAuthData = function(customerId, key, secret) {
    const nonce = Date.now();
    const message = nonce + customerId + key;
    const signature = crypto
    .createHmac('sha256', new Buffer(secret, 'utf8'))
    .update(message)
    .digest('hex')
    .toUpperCase();

    return { key, signature, nonce };
};

const getAskPrice = function() {
  return got(TICKET_URL).then(response =&gt; {
    const result = JSON.parse(result);
    const ask = Number(result.ask);

    if (!isNaN(ask)) {
      return ask;
    }

    throw new Error(`Could not parse the ask price from [${BTCEUR_TICKER_URL}] response:\n${response.body}`);
  });
});

const buyLimitOrder = function(customerId, key, secret, value, askPrice) {
    const amount = value / askPrice;
    const roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);

    const order = {
        ...createAuthData(customerId, key, secret),
        amount: roundedAmount,
        price: askPrice
    };

    console.log(`Buying ${roundedAmount} of BTC for price ${askPrice} for total value of ${roundedAmount * askPrice}.`);

    return got.post(BUY_LIMIT_ORDER_URL, {
        body: order,
        form: true
    }).then(response =&gt; {
        return JSON.parse(response.body);
    });
};

const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
    return getAskPrice()
        .then(askPrice =&gt; buyLimitOrder(customerId, key, secret, value, askPrice));
};

exports.handler = (event, context, callback) =&gt; {
    doWithBitstampProperties()
        .then(bitstampProperties =&gt; 
            getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
                .then(result =&gt; callback(null, result))
                .catch(error =&gt; callback(error)))
        .catch(error =&gt; callback(error));
};


A few other potential changes...


Preferring const over let to denote a variable can't be reassigned (but still mutable)
Date.now() can be used instead of (new Date()).getTime()
Returning early to prevent else blocks
Moving BITSTAMP_PROPERTIES declaration closer to where it's assigned
Using the spread operator to prevent mutating orders (Note: this is currently a stage 3 draft at time of writing)
Using template literals for more succinct strings
Using array matching instead of indices to for customerId, key and secret
Using node-fetch instead of got to be consistent with the browser's Fetch API. JSON requests would be of the form fetch(...).then(res =&gt; res.json()).then(data =&gt; ...) instead of calling JSON.parse