This is the 12th day of my participation in Gwen Challenge
Working with Promises
introduce
The advent of Promises provides a better way to handle asynchronous code in JavaScript. There are already several third-party libraries that implement similar functionality, such as:
Q
when
WinJS
RSVP.js
The Promise libraries above, and the ES2015 JavaScript specification (ES6) also support Promises.
For a list of browsers that support Promise, see Can I Use
Why use Promise
In JavaScript, asynchronous operations are very common. They can be used to request network resources or access local disks, communicate with Web workers and Service workers, and use timers.
In most cases, when these operations have been processed, they are communicated through callback functions or events. In the era of simple web pages, these methods work well, but they don’t work as well in large applications.
Old method: Use events
Using events to report asynchronous results has a number of obvious drawbacks:
- The need to put the code in each event handler makes the code too scattered;
- Between defining a handler and receiving an event, there may be critical states that cause disorder;
- To maintain synchronization, it is often necessary to define a class or global variable to maintain state.
These issues complicate asynchronous processing, so look for any XMLHttpRequest code.
Old method: Use callbacks
Another option is to use callbacks, typically using an anonymous function. It looks something like this:
function isUserTooYoung(id, callback) {
openDatabase(function(db) {
getCollection(db, 'users'.function(col) {
find(col, {'id': id}, function(result) {
result.filter(function(user) {
callback(user.age < cutoffAge);
});
});
});
});
}
Copy the code
There are two problems with this approach:
- Complex reading comprehension: The more callbacks used, the deeper the nesting, and the harder the code is to understand and analyze;
- Error handling is complex: what if, for example, one of the functions receives an invalid argument?
The use of Promise
Promises provide a standardized way to manage asynchronous operations and handle exceptions. Using the previous code as an example, using Promise can be even simpler:
function isUserTooYoung(id) {
return openDatabase() // returns a promise
.then(function(db) {return getCollection(db, 'users'); }) .then(function(col) {return find(col, {'id': id}); }) .then(function(user) {returnuser.age < cutoffAge; }); }Copy the code
You can think of a Promise as an object that waits for an asynchronous operation to end, then calls the next function in turn. We can pass in the next function by calling the.then() method. When the asynchronous function ends, its results are passed to the Promise, which is then passed to the next function (as arguments).
Note that there may be many calls to.then(), each of which waits for the previous Promise to end, executes the next function, and returns the result to the Promise if necessary. This way we can painlessly link synchronous and asynchronous calls. It simplifies our code so much that most new specifications now return promises from asynchronous methods.
Promise term
When we use promises, we often come across terms related to callbacks and other asynchronous operations.
In the example below, we need to convert an asynchronous task that sets the address of the image into a Promise.
function loadImage(url) {
// wrap image loading in a promise
return new Promise(function(resolve, reject) {
// A new promise is "pending"
var image = new Image();
image.src = url;
image.onload = function() {
// Resolving a promise changes its state to "fulfilled"
// unless you resolve it with a rejected promise
resolve(image);
};
image.onerror = function() {
// Rejecting a promise changes its state to "rejected"
reject(new Error('Could not load image at ' + url));
};
});
}
Copy the code
Promises have the following states:
- In the (
Pending
) – The asynchronous operation is still in progress,Promise
The results haven’t come out yet - Has been completed,
Fulfilled
) – The asynchronous operation has ended,Promise
A returned value is obtained - Terminated (
Rejected
) – Asynchronous operation failed,Promise
Unable to complete, and a failure reason is returned
This is a big pity, which is settled, which refers to a Promise Fulfilled, which is either Fulfilled or Rejected.
How to use Promise
Write a simple promise
Here’s a typical example of how to make a Promise:
var promise = new Promise(function(resolve, reject) {
// Do something asynchronous, etc
if (/* The expected result */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke")); }});Copy the code
The Promise constructor takes one argument — a two-argument callback, resolve and Reject. We can do all sorts of things in callback functions, such as asynchronous operations, calling resolve if everything is okay, reject otherwise.
Handling errors with Reject is a bit like the throw method used in normal JavaScript, but is not required. The biggest advantage of the Reject Error object is that it captures the trace stack, making it useful for debugging.
With Promise created, let’s look at how to use it:
promise.then(function(result) {
console.log("Success!", result); // "Stuff worked!"
}, function(err) {
console.log("Failed!", err); // Error: "It broke"
});
Copy the code
The.then() method takes two arguments, one for a successful callback and one for a failed callback. Both are optional, and you can add just one. The more common way is to handle success results with.then() and errors with.catch().
promise.then(function(result) {
console.log("Success!", result);
}).catch(function(error) {
console.log("Failed!", error);
})
Copy the code
The.catch() method is also simple, equivalent to passing only failed callbacks — then(undefined, func) — in the then method, but more readable. Note that the above two examples are executed differently. The latter corresponds to:
promise.then(function(response) {
console.log("Success!", response);
}).then(undefined.function(error) {
console.log("Failed!", error);
})
Copy the code
The difference is subtle, but useful. In a Promise, if there is a reject error and no reject callback in the current THEN () method, it looks in the next THEN () method (or in the catch() method, since they are equivalent).
The first example above uses then(func1, func2), func1 and func2 can only be called one, not both. But in the second example then(func1).then(undefined, func2), and the third example then(func1).catch(func2), fun2 is also called if func1 needs reject in an error, Because they are two separate steps in the chain of execution.
Promise chains: Then and catch
On a Promise, we can add multiple THEN () and catch() to form a Promise chain. In the Promise chain, the result returned by the previous function becomes the argument received by the next function.
Then
The then() method takes function arguments to process the return result of the Promise. When a Promise returns successfully,.then() extracts the value it returns (the value of resolve in the Promise), executes the callback function, and wraps the return value in a new Promise.
You can think of then() as the try part of a try/catch block.
Remember our previous example of calling multiple operations in a row.
function isUserTooYoung(id) {
return openDatabase() // returns a promise
.then(function(db) {return getCollection(db, 'users'); }) .then(function(col) {return find(col, {'id': id}); }) .then(function(user) {returnuser.age < cutoffAge; }); }Copy the code
The value returned in the first Promise is passed through the then() method. The then() method returns either a Promise, which is passed on, or a value that becomes an argument to a later function. With then(), you can link any number of actions.
Catch
Promises also provide simple error handling. When a Promise is rejected (or thrown), it calls the function by jumping to the first catch().
You can think of the call chains before the catch as being wrapped in an invisible try{} method.
In the example below, we load an image with loadImage() and then() to perform a series of transformations. If something goes wrong (both the original Promise and any subsequent steps), it jumps straight to the catch() statement.
Only the last then() statement adds the image to the DOM, and until then we all return the same image to pass to the next THEN ().
function processImage(imageName, domNode) {
// returns an image for the next step. The function called in
// the return statement must also return the image.
// The same is true in each step below.
return loadImage(imageName)
.then(function(image) {
// returns an image for the next step.
return scaleToFit(150.225, image);
})
.then(function(image) {
// returns the image for the next step.
return watermark('Google Chrome', image);
})
.then(function(image) {
// Attach the image to the DOM after all processing has been completed.
// This step does not need to return in the function or here in the
// .then() because we are not passing anything on
showImage(image);
})
.catch(function(error) {
console.log('We had a problem in running processImage', error);
});
}
Copy the code
Within a Promise chain, we can also do some recovery using catch methods. For example, in the following code, if something goes wrong in the loadImage and scaleToFit phases, we provide an emergency image in the catch method and pass it in to the later THEN method.
function processImage(imageName, domNode) {
return loadImage(imageName)
.then(function(image) {
return scaleToFit(150.225, image);
})
.catch(function(error) {
console.log('Error in loadImage() or scaleToFit()', error);
console.log('Using fallback image');
return fallbackImage();
})
.then(function(image) {
return watermark('Google Chrome', image);
})
.then(function(image) {
showImage(image);
})
.catch(function(error) {
console.log('We had a problem with watermark() or showImage()', error);
});
}
Copy the code
Note that the Promise chain continues after the catch() statement is executed, and does not stop until the last then() or catch() statement is executed.
Synchronous operation
Not all of the functions that Promise executes need to return a Promise. If the function is synchronous and can be executed directly, there is no need to return a Promise.
The scaleToFit function below is part of the image-processing link and does not return a Promise.
function scaleToFit(width, height, image) {
image.width = width;
image.height = height;
console.log('Scaling image to ' + width + ' x ' + height);
return image;
}
Copy the code
However, this function needs to return the passed image so that it can be passed on to the next function.
Promise.all
Many times we need to wait for a series of asynchronous operations to complete successfully before performing some action. Promise.all returns a Promise, and resolve is executed if all incoming promises have completed; If any of the incoming promises fail, reject is executed, and the reason for the error is returned. This is useful for situations where we need to ensure that a set of asynchronous actions are complete before proceeding to the next step.
In the following example, promisE1 and promise2 both return promises. We want to continue after they are both loaded. We pass both promises into promise.all, and if either request fails, promise.all rejects the faulty Promise. If both requests succeed, promise. all is resolved as an array of the return values of both promises.
var promise1 = getJSON('/users.json');
var promise2 = getJSON('/articles.json');
Promise.all([promise1, promise2]) // Array of promises to complete
.then(function(results) {
console.log('all data has loaded');
})
.catch(function(error) {
console.log('one or more requests have failed: ' + error);
});
Copy the code
Note: Even if one of these Promise reject results in the whole promise.all reject, the remaining promises are still executed, just not returned via promise.all.
Promise.race
Another method that can be used is promise.race. Promise.race also receives a list of promises, and when a Promise completes first, the promise.race completes. If the fastest Promise is resolved, promise. race is resolved; if the Promise is rejected, promise. race is rejected for the same reason.
The following code shows an example of promise.race:
Promise.race([promise1, promise2])
.then(function(value) {
console.log(value);
})
.catch(function(reason) {
console.log(reason);
});
Copy the code
If one of the promises resolves first, the contents of the THEN block are immediately executed and the value of RESOLVE is noted down.
If one of the promises is rejected, the catch is immediately executed and the reason is noted down.
As the name suggests, promise. race is a way to make promises race to whichever comes back first. This may seem attractive, but it’s easy to overlook some problems, such as the following example:
var promise1 = new Promise(function(resolve, reject) {
// something that fails
});
var promise2 = new Promise(function(resolve, reject) {
// something that succeeds
});
Promise.race([promise1, promise2])
.then(function(value) {
// Use whatever returns fastest
})
.catch(function(reason) {
console.log(reason);
});
Copy the code
At first glance, this code seems to be racing two promises, reject and resolve, to see which one returns first. However, if one Promise is rejected, promise. race immediately rejects, even if the other Promise is soon resolved.
Therefore, if promise1 rejects before promise2, race will reject immediately, even though promise2 will resolve immediately. Promise.race by itself does not guarantee that the first resolve Promise will be returned.
Another interesting use is this:
var promise1 = new Promise(function(resolve, reject) {
// get a resource from the Cache
});
var promise2 = new Promise(function(resolve, reject) {
// Fetch a resource from the network
});
Promise.race([promise1, promise2])
.then(function(resource) {
// Use the fastest returned resource
})
.catch(function(reason) {
console.log(reason);
});
Copy the code
This example seems to want the network resource to compete with the cache resource, and whichever returns first will be used. However, both the Cache API and the Fetch API can return a result that is not what we want (Fetch also returns in the case of 404, and Caches can return the wrong resource when it is not available). In this example, if the resource in the cache is not available, but because it usually returns faster, promise.race resolves with the error result it returns and ignores network resources that might be available immediately.
For the Cache & Network Race section, see Offline Cookbook.