0

I'm new to Node/Express and am trying to use Promises to executive successive API calls to Apple's CloudKit JS API.

I'm unclear on how to put the functions in sequence and pass their respective return values from one function to the next.

Here's what I have so far:

var CloudKit = require('./setup')

//----
var fetchUserRecord = function(emailConfirmationCode){
  var query = { ... }

  // Execute the query
  CloudKit.publicDB.performQuery(query).then(function (response) {
    if(response.hasErrors) {
      return Promise.reject(response.errors[0])
    }else if(response.records.length == 0){
      return Promise.reject('Email activation code not found.')
    }else{
      return Promise.resolve(response.records[0])
    }
  })
}

//-----
var saveRecord = function(record){
  // Update the record (recordChangeTag required to update)
  var updatedRecord = { ... }

  CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
    if(response.hasErrors) {
      Promise.reject(response.errors[0])
    }else{
      Promise.resolve()
    }
  })
}

//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){

  CloudKit.container.setUpAuth() //<-- This returns a promise
  .then(fetchUserRecord) //<-- This is the 1st function above
  .then(saveRecord(record)) //<-- This is the 2nd function above
    Promise.resolve('Success!')
  .catch(function(error){
    Promise.reject(error)
  })

}

I get an error near the end: .then(saveRecord(record)) and it says record isn't defined. I thought it would somehow get returned from the prior promise.

It seems like this should be simpler than I'm making it, but I'm rather confused. How do I get multiple Promises to chain together like this when each has different resolve/reject outcomes?

Clifton Labrum
  • 13,053
  • 9
  • 65
  • 128
  • What is the value of `response.records[0]` before the Promise is resolved in `fetchUserRecord`? – Jamie Weston Aug 21 '18 at 19:54
  • 1
    This might also be helpful: [How to chain and share prior results with promises](https://stackoverflow.com/questions/28714298/how-to-chain-and-share-prior-results-with-promises/28714863#28714863) – jfriend00 Aug 21 '18 at 21:04

2 Answers2

4

There are few issues in the code.

First: you have to pass function to .then() but you actually passes result of function invocation:

.then(saveRecord(record))

Besides saveRecord(record) technically may return a function so it's possible to have such a statement valid it does not seem your case. So you need just

.then(saveRecord)

Another issue is returning nothing from inside saveRecord and fetchUserRecord function as well.

And finally you don't need to return wrappers Promise.resolve from inside .then: you may return just transformed data and it will be passed forward through chaining.

var CloudKit = require('./setup')

//----
var fetchUserRecord = function(emailConfirmationCode){
  var query = { ... }

  // Execute the query
  return CloudKit.publicDB.performQuery(query).then(function (response) {
    if(response.hasErrors) {
      return Promise.reject(response.errors[0]);
    }else if(response.records.length == 0){
      return Promise.reject('Email activation code not found.');
    }else{
      return response.records[0];
    }
  })
}

//-----
var saveRecord = function(record){
  // Update the record (recordChangeTag required to update)
  var updatedRecord = { ... }

  return CloudKit.publicDB.saveRecords(updatedRecord).then(function(response) {
    if(response.hasErrors) {
      return Promise.reject(response.errors[0]);
    }else{
      return Promise.resolve();
    }
  })
}

//----- Start the Promise Chain Here -----
exports.startActivation = function(emailConfirmationCode){

  return CloudKit.container.setUpAuth() //<-- This returns a promise
    .then(fetchUserRecord) //<-- This is the 1st function above
    .then(saveRecord) //<-- This is the 2nd function above
    .catch(function(error){});
}

Don't forget returning transformed data or new promise. Otherwise undefined will be returned to next chained functions.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
  • 1
    btw `return Promise.resolve()` looks redundant but if I drop that or just put `return;` during chaining you will get result/data of previous `.then()` that could be logically invalid. I cannot say what if it's possible to put something different there. – skyboyer Aug 21 '18 at 20:17
  • Thank you very much for your thorough reply. It's clearer now, but I have a follow-up question. When successful, the `fetchUserRecord` function returns a `record` object. How does that value get passed on to the `saveRecord` function? – Clifton Labrum Aug 21 '18 at 21:38
0

Since @skyboyer helped me figure out what was going on, I'll mark their answer as the correct one.

I had to tweak things a little since I needed to pass the returned values to subsequent functions in my promise chain. Here's where I ended up:

exports.startActivation = function(emailConfirmationCode){
  return new Promise((resolve, reject) => {

    CloudKit.container.setUpAuth()
      .then(() => {
        return fetchUserRecord(emailConfirmationCode)
      })
      .then((record) => {
        resolve(saveRecord(record))
      }).catch(function(error){
        reject(error)
      })

  })
}
Clifton Labrum
  • 13,053
  • 9
  • 65
  • 128
  • 1
    since `setUpAuth` returns a promise you don't need to need to play with `new Promise` and resolve it manually. Also you don't need rethrow error in `catch()` to reject it again `function(emailConfirmationCode){ return CloudKit.container.setUpAuth().then(() => { return fetchUserRecord(emailConfirmationCode) }).then(saveRecord); }` – skyboyer Aug 22 '18 at 05:30
  • btw next time better edit original question instead of asking something additional in subsequent answer. it could be confusing for other readers this way. Also while 'answering answer' there are only comments available. Comments have limited formatting abilities :( – skyboyer Aug 22 '18 at 05:33
  • Great suggestions, thank you! You've been very helpful. – Clifton Labrum Aug 22 '18 at 05:37