Use Promise
You can start to develop a Web app based on the information provided until now, but promises allow you to efficiently implement complicated logic. It is recommended to use promises for new Web app development.
Promises are a technology to efficiently implement asynchronous calls. The Kii Cloud SDK supports implementations based on Promises/A+.
You can use APIs of the Kii Cloud SDK with promises to achieve the same functions that are provided with callbacks. Until now we have seen how to implement calls to Kii Cloud with callbacks. The same processes can be implemented with promises.
Promises themselves and their usage are separate from the Kii Cloud features. Refer to information on the Internet and specialized books. This topic outlines promises and information specific to the Kii Cloud SDK.
The APIs basically support both callbacks and promises. It would be ideal to consistently use either of them within a Web app. However, you can also use callbacks and promises per API and/or gradually modify existing Web apps to switch to promises.
Overview of promises
Promises can simplify complicated processes with many levels of nested callbacks.
See the below pseudo code which implements asynchronous processes with callback functions. The processes are executed in the order of api1()
, api2()
, api3()
, and api4()
.
api1(param1, function(param2) {
api2(param2, function(param3) {
api3(param3, function(param4) {
api4(param4, function(param5) {
console.log(param5);
});
});
});
Sequential API calls make the nested structure deeper and more complex. It brings so-called "callback hell". Especially, the problem is that it is difficult to grasp the code structure which should flow from top to down by rights. As shown in the Execution Sequence section for callbacks, it is hard to understand the code because the order of written lines does not match the execution sequence. The inner part of the callback function is executed at the end.
See the below pseudo code which is rewritten from the above code with promises.
api1(param1).then(
function(params) {
return api2(params);
}
).then(
function(params) {
return api3(params);
}
).then(
function(params) {
return api4(params);
}
).then(
function(params) {
console.log(params);
}
);
Sequential API calls do not make the nesting too deeper and the code structure which flows from top to down is maintained.
These are simple models for explanation. The difference is clearer in actual implementation examples. See the below examples of Kii Cloud implementation.
The promise version is easier to read and understand the program structure.
Implementation in Hello Kii
Hello Kii supports implementation with promises.
Update the below lines in the index.html
file to use promises.
Before
<html>
<head>
......
<script type="text/javascript" src="login-page-callback.js"></script>
<script type="text/javascript" src="list-page-callback.js"></script>
......
After
<html>
<head>
......
<script type="text/javascript" src="login-page-promise.js"></script>
<script type="text/javascript" src="list-page-promise.js"></script>
......
You will not see any difference in the behavior of Hello Kii after this modification because the implemented functions are the same.
Compare the delta between the *-callback.js
file and the *-promise.js
file to see how the same functions are differently implemented.
Implementation sample of the login API
Let's take a look at the login API in the promise implementation. The login API is implemented in the performLogIn()
function in the login-page-callback.js
file and the login-page-promise.js
file as below.
Callbacks
KiiUser.authenticate(username, password, {
success: function(theUser) {
console.log("User authenticated: " + JSON.stringify(theUser));
},
failure: function(theUser, errorString) {
console.log("Unable to authenticate user: " + errorString);
}
});
Promise
KiiUser.authenticate(username, password).then(
function(theUser) {
console.log("User authenticated: " + JSON.stringify(theUser));
}
).catch(
function(error) {
var errorString = error.message;
console.log("Unable to authenticate user: " + errorString);
}
);
Both use the username and password received from the screen to call the authenticate()
method.
The callback implementation specifies the callback functions in the success
and failure
blocks as the arguments of the authenticate()
method. These functions are called at the completion of the API call.
With promises, the API does not directly receive the callback functions but a Promise
object. In Hello Kii, the function to execute after a successful API call is specified in the then()
method of the Promise
object and the function to execute after a failed API call in the catch()
method of the Promise
object. We will see other ways to specify functions in Notes for Referring to the JSDoc.
In the promise implementation, as with the callbacks, the authenticate()
method immediately returns control to the caller when the method is called. After the authenticate()
method completes, the function specified in the then()
method or the catch()
method is called from the promise libraries.
This section explains only the different writing styles between callbacks and promises. However, the key strength of promises is that you can avoid the callback hell with promises. See Example of Sequential Execution for chaining processes of Hello Kii with promises.
Processing multiple return values
You cannot directly receive multiple return values with promises and need to be careful with some APIs. The below sample code indicates a promise implementation that receives multiple return values when an API call succeeds.
bucket.executeQuery(queryObject).then(
function(params) {
var queryPerformed = params[0];
var result = params[1];
var nextQuery = params[2];
console.log("Execute query: got " + result.length + " objects");
}
);
This is an example of the API that performs a query on the data listing screen. It is implemented in the list-page-promise.js
file.
The function in the then()
method of the promise receives one array params
and then gets queryPerformed
, result
, and nextQuery
from the array elements while the corresponding callback function receives queryPerformed
, result
, and nextQuery
at once in the success
block.
Similarly, if the query fails, the function in the catch()
method of the promise receives error
and then gets the error message from the message
property of error
while the corresponding callback function receives queryPerformed
and errorString
at once in the failure
block. See the sample code in Implementation Sample of the Login API.
The way to get values is different among APIs. Check the "Promise" tab of the sample code in the JavaScript Programming Guide to see the specification of the return values.
Sequential execution of APIs
Next, we walk through how to execute APIs sequentially. The primary purpose of using promises is to execute APIs efficiently with a promise chain.
APIs that access the network through the Kii Cloud SDK return a Promise
object. You can chain processes by passing a Promise
object from the function specified in the first then()
method to the next then()
method.
See the below pseudo code.
// Step A
KiiCloudAPI.api1(param).then(
function(params) {
// api1 succeeded.
// Step B
......
return KiiCloudAPI.api2(params);
}
).then(
function(params) {
// api2 succeeded.
// Step C
......
return KiiCloudAPI.api3(params);
}
).then(
function(params) {
// api3 succeeded.
// Step D
}
).catch(
function(error) {
// API failed.
// Step E
console.log("failed to execute: " + error.message);
}
);
// Step F
In the above sample code, three APIs, KiiCloudAPI.api1()
, KiiCloudAPI.api2()
, and KiiCloudAPI.api3()
are called as APIs of the Kii Cloud SDK that involve network access.
As commented in the source code, when an API call succeeds, the function in the then()
method just below the succeeded call is executed.
When an API call fails, the function in the catch()
method just below the failed call is executed. In the above sample code, the catch()
method appears only once at the bottom. It means that the same error processing is performed when any of the three APIs fails. Without the catch()
method, any errors thrown in asynchronous APIs are ignored.
See the below table for the combinations of success and failure of each API and code blocks to be executed. The "Execution Path" column in the table indicates the execution order of the blocks commented with // Step X
. As with callbacks, note that Step F
is executed just after KiiCloudAPI.api1()
is called and then the other functions are executed from above.
api1() | api2() | api3() | Execution Path |
---|---|---|---|
Success | Success | Success | // Step A → // Step F → // Step B → // Step C → // Step D |
Success | Success | Failure | // Step A → // Step F → // Step B → // Step C → // Step E |
Success | Failure | - | // Step A → // Step F → // Step B → // Step E |
Failure | - | - | // Step A → // Step F → // Step E |
In order to stop processing when an error occurs while a chain is being processed, add an error handling step, for example, by calling the Promise.reject()
method. Especially, note that the catch()
method in the middle of a promise chain can handle only errors that occur above the method. The next then()
method will be executed if the reject()
method does not exist. Check technical information on the Internet for details of the reject()
method.
Example of sequential execution
Let's rewrite the promise version of Hello Kii as an example of sequential execution of APIs.
The performLogIn()
function in the login screen and the openListPage()
function in the data listing page have these sets of code respectively. Comments and unessential processes for this section are omitted below.
Logging In
var username = document.getElementById("username-field").value;
var password = document.getElementById("password-field").value;
KiiUser.authenticate(username, password).then(
function(theUser) {
console.log("User authenticated: " + JSON.stringify(theUser));
}
).catch(
function(error) {
var errorString = error.message;
alert("Unable to authenticate user: " + errorString);
}
);
Fetching All KiiObjects
var queryObject = KiiQuery.queryWithClause(null);
queryObject.sortByDesc("_created");
var bucket = KiiUser.getCurrentUser().bucketWithName(BUCKET_NAME);
bucket.executeQuery(queryObject).then(
function(params) {
var queryPerformed = params[0];
var result = params[1];
var nextQuery = params[2];
console.log("Execute query: got " + result.length + " objects");
).catch(
function(error) {
var errorString = error.message;
alert("Unable to execute query: " + errorString);
}
);
Let's combine these two processes and sequentially execute the authenticate()
method and the executeQuery()
method. This means that we are changing the process so that clicking the "Log In" button let the specified user log in and outputs the number of KiiObjects in myBucket
, the bucket in the user's scope to the console.
First, embed the lines to fetch all KiiObjects in the function within the then()
method of the Promise
object returned from the authenticate()
method (Let's call this then()
method the first then()
method). Note that we don't embed the then()
method of the Promise
object returned from the executeQuery()
method in order to avoid deep nesting that is expected when you use callbacks. Instead, set the Promise
object returned by the executeQuery()
method as the return value of the function within the first then()
method.
We have made the below code, which is still incomplete.
var username = document.getElementById("username-field").value;
var password = document.getElementById("password-field").value;
KiiUser.authenticate(username, password).then(
function(theUser) {
console.log("User authenticated: " + JSON.stringify(theUser));
var queryObject = KiiQuery.queryWithClause(null);
queryObject.sortByDesc("_created");
var bucket = KiiUser.getCurrentUser().bucketWithName(BUCKET_NAME);
return bucket.executeQuery(queryObject);
}
).catch(
function(error) {
var errorString = error.message;
alert("Unable to execute query: " + errorString);
}
);
Next, add the second then()
method to chain the process to be executed when the executeQuery()
method succeeds. At the same time, modify the message in the catch()
method so that you can use it for both of the authenticate()
method and the executeQuery()
method.
Finally, we have the code below.
var username = document.getElementById("username-field").value;
var password = document.getElementById("password-field").value;
KiiUser.authenticate(username, password).then(
function(theUser) {
console.log("User authenticated: " + JSON.stringify(theUser));
var queryObject = KiiQuery.queryWithClause(null);
queryObject.sortByDesc("_created");
var bucket = KiiUser.getCurrentUser().bucketWithName(BUCKET_NAME);
return bucket.executeQuery(queryObject);
}
).then(
function(params) {
var queryPerformed = params[0];
var result = params[1];
var nextQuery = params[2];
console.log("Execute query: got " + result.length + " objects");
}
).catch(
function(error) {
var errorString = error.message;
alert("Failed to execute: " + errorString);
}
);
The key is that the Promise
object returned by the asynchronous API is treated as the return value of the function within the then()
method and chained to the next then()
method. This way, you can modify the sample code to achieve desired functionality in sequential execution of multiple APIs.
Notes for referring to the JSDoc
Sample code in the JSDoc is written to define error handling as the second argument of the then()
method as below.
// Example to use Promise
KiiUser.authenticate("myusername", "mypassword").then(
function(theAuthenticatedUser) {
// Do something with the authenticated user.
},
function(error) {
// Do something with the error response.
}
);
This format forms a complete promise code that specifies processes at success and failure altogether.
To create a promise chain that is similar to the one explained in this topic, modify it to use the catch()
method as below.
// Example to use Promise
KiiUser.authenticate("myusername", "mypassword").then(
function(theAuthenticatedUser) {
// Do something with the authenticated user.
}
).catch(
function(error) {
// Do something with the error response.
}
);
You can specify how to handle the error case per API in the format in the JSDoc. If you use the catch()
method, note that the code execution continues to any next then()
method after the catch()
method completes.
What's next?
We will wrap up the tutorial by presenting some hints to understand Kii Cloud. We will also introduce some features that will be useful when implementing some real mobile apps.
Go to Hints for Next Step.
If you want to learn more...
- The JavaScript Programming Guide also has a topic about promises. See Using Promises for more information.