Promise, in simple terms, is a container that holds the result of an event (usually an asynchronous operation) that will only end in the future. Syntax-wise, a Promise is an object from which you can obtain messages about the asynchronous operation.
With Promise objects, you can express asynchronous operations in a synchronous flow, avoiding nested callback functions. In addition, Promise objects provide a unified interface that makes it easier to control asynchronous operations.
However, Promises also have some drawbacks:
- First, Promises cannot be canceled. Once created, they will immediately execute and cannot be canceled midway.
- If no callback function is set, errors thrown inside the Promise will not be reflected outside.
- When in the pending state, it is not possible to know which stage the operation is currently at (just started or about to complete).
Characteristics of Promises#
- The state of the object is not affected by the external environment. A Promise object represents an asynchronous operation and has three states: pending (in progress), fulfilled (successfully completed), and rejected (failed).
- Once the state changes, it will not change again, and the result can be obtained at any time. When the state of a Promise object changes, it either succeeds or fails. Once one of these two situations occurs, the state solidifies and becomes resolved. After the change occurs, even if you add callback functions, you will still get the same result.
Usage#
Creating a Promise instance#
A Promise object is a constructor used to generate Promise instances.
const promise = new Promise(function(resolve, reject) {
if (/* asynchronous operation is successful */){
resolve(value);
} else {
reject(error);
}
});
The resolve function is used to change the state of the Promise object from "pending" to "fulfilled" (i.e., from pending to resolved). It is called when the asynchronous operation is successful and passes the result of the asynchronous operation as a parameter.
The reject function is used to change the state of the Promise object from "pending" to "rejected" (i.e., from pending to rejected). It is called when the asynchronous operation fails and passes the error thrown by the asynchronous operation as a parameter.
The then method#
Next, use the then method to specify the callback functions for the resolved and rejected states:
promise.then(function(value) {
// success
}, function(error) {
// failure
});
The first function is called when the state object changes to rejected, the second function is called when it changes to resolved, and the second function is optional.
The then method returns a new Promise instance (note that it is not the original Promise instance). Therefore, you can use chain writing, i.e., call another then method after the first then method.
promise.then(function(value) {
}.then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
The catch method#
Used to specify the callback function when an error occurs.
promise.then(function(value) {
// success
}, function(error) {
// failure
}).catch(function(error) {
// handle the error that occurred
console.log('An error occurred!', error);
});
If the asynchronous operation throws an error, the state will change to rejected, and the callback function specified by catch() will be called to handle the error. In addition, if the callback function specified by then() throws an error during execution, it will also be caught by the catch() method.
Unlike traditional try/catch blocks, if no callback function is specified to handle the error using catch(), the error thrown by the Promise object will not be passed to the outer code and will not have any effect.
The finally method#
Used to specify operations that will be executed regardless of the final state of the Promise object.
promise.then(function(value) {
// success
}, function(error) {
// failure
}).catch(function(error) {
// handle the error that occurred
console.log('An error occurred!', error);
}).finally(function(){
alert("finish");
});
The callback function of the finally method does not accept any parameters, meaning that you cannot know the final state.
Example of using jQuery, Ajax, and Promises#
I previously learned about jQuery and Ajax, so I found an API online to test it:
const apiUrl = "https://suggest.taobao.com/sug?code=utf-8&q=电脑&callback=cb";
$("#bt").click(apiUrl,function(event){
const promise = new Promise(function(resolve,reject){
$.ajax({
url: event.data,
type:"get",
dataType:"jsonp",
headers: {
Accept: "application/json; charset=utf-8",
},
success: function(data) {
resolve(data);
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
reject(XMLHttpRequest,textStatus,errorThrown)
},
})
})
promise.then(function(data){
console.log(data);
let x = data.result;
for(let i = 0 ; i<data.result.length;i++){
console.log(x[i][0]);
}
},function(XMLHttpRequest, textStatus, errorThrown){
alert(XMLHttpRequest);
alert(textStatus);
alert(errorThrown);
}).catch(function(error){
console.log(error);
}).finally(function(){
alert("finish");
});
});
Result:
Issues Encountered#
I didn't have any major issues with learning the basics of Promises, but I did encounter a cross-origin issue when making the Ajax request. The error message was:
Access to XMLHttpRequest at 'api URL' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
What is this...?
After some research, I learned about the concept of cross-origin.
Each website can only read data from the same origin, where the same origin refers to the combination of hostname (domain), protocol (http/https), and port number. Without explicit authorization, you cannot read or write resources from another origin. It is the core and fundamental security feature of browsers. As long as there is a difference in any of these components, it is considered cross-origin.
The XMLHttpRequest in Ajax is subject to the same-origin policy and can only access data from the same origin. That's why the error occurred.
So how do we solve it?
Most of the articles I found suggested that you need to set up request permissions with the backend, but this is a wild API... Can I really ask someone to do this and that for me?
Until I found a solution: jsonp
JSONP:
- It is a synchronous mode that uses the script tag to import data. When using the script tag for cross-origin, it is not recommended to preload the data. It should be loaded on demand.
- When you need the data, create a script tag and put the required data in the src attribute. Use the onload event to listen for the arrival of the request, and once it is complete, call the callback function to pass back the data (asynchronous loading).
- JSONP can only use GET requests, not POST requests.
So why does this type allow cross-origin requests?
Tags with src attributes, such as script, img, iframe, and link, are not subject to the same-origin policy. However, the browser restricts the permissions of JavaScript loaded through src, allowing only reading and not writing.
In summary: changing the dataType from json to jsonp solves the issue.