preface
The basic use of Promise can be found in Teacher Ruan Yifeng’s introduction to ECMAScript 6.
Let’s talk about something else.
The callback
When we talk about promises, we usually start with callbacks or callback hell, so what’s the downside of using callbacks?
1. Callback nesting
With callbacks, we would most likely write the business code as follows:
doA( function(){
doB();
doC( function(){ doD(); } ) doE(); }); doF();Copy the code
Of course, this is a simplified form. After some simple thinking, we can determine the order of execution as follows:
doA()
doF()
doB()
doC()
doE()
doD()
Copy the code
However, in actual projects, the code will be more chaotic. In order to troubleshoot problems, we need to bypass many eyesores and constantly jump between functions, making the difficulty of troubleshooting problems doubled.
The problem, of course, is that nesting is so incompatible with linear thinking that we have to spend more time thinking about the actual order of execution. Nesting and indentation are just red herring in the process.
Of course, it’s not the worst thing that goes against people’s linear thinking. In fact, we add all kinds of logical judgments to our code, like in the example above, doD() must be completed after doC(), and what if doC() fails? Are we trying doC() again? Or do you just go to some other error handler? When we add these judgments to the process, the code quickly becomes too complex to maintain and update.
2. Inversion of control
When we write code normally, we have control over our code. However, when we use a callback, whether the callback will continue to execute depends on the API using the callback, as in:
// Whether the callback function is executed depends on the Buy module
import {buy} from './buy.js';
buy(itemData, function(res) {
console.log(res)
});
Copy the code
There is no problem with the FETCH API that we often use, but what if we use a third-party API?
When you call a third party’s API, will the other party execute your callback more than once because of some error?
To avoid this problem, you can add judgments to your own callback function, but what if the callback is not executed because of some error? What if the callback sometimes executes synchronously and sometimes asynchronously?
Let’s summarize the situation:
- The callback function is executed multiple times
- The callback function did not execute
- Callbacks are sometimes executed synchronously and sometimes asynchronously
In each of these cases, you’ll probably have to do something in the callback, and every time the callback is executed, you’ll have to do something, which leads to a lot of duplicate code.
The callback hell
Let’s start with a simple example of callback hell.
Now to find the largest file in a directory, the procedure should be:
- with
fs.readdir
Get a list of files in the directory; - Loop through the file using
fs.stat
Obtaining File Information - Compare to find the largest file;
- Invokes the callback with the file name parameter of the largest file.
The code is:
var fs = require('fs');
var path = require('path');
function findLargest(dir, cb) {
// Read all files in the directory
fs.readdir(dir, function(er, files) {
if (er) return cb(er);
var counter = files.length;
var errored = false;
var stats = [];
files.forEach(function(file, index) {
// Read file information
fs.stat(path.join(dir, file), function(er, stat) {
if (errored) return;
if (er) {
errored = true;
return cb(er);
}
stats[index] = stat;
// Count the number of files, read 1 file information, count minus 1, when 0, it is finished reading, then perform the final comparison operation
if (--counter == 0) {
var largest = stats
.filter(function(stat) { return stat.isFile() })
.reduce(function(prev, next) {
if (prev.size > next.size) return prev
return next
})
cb(null, files[stats.indexOf(largest)])
}
})
})
})
}
Copy the code
Use mode:
// Find the largest file in the current directory
findLargest('/'.function(er, filename) {
if (er) return console.error(er)
console.log('largest file was:', filename)
});
Copy the code
You can copy the above code into a file such as index.js, and then execute Node index.js to print out the name of the largest file.
After this example, let’s talk about some other problems with callback hell:
1. Hard to reuse
Once the order of callbacks is determined, it is difficult to reuse some of them.
For example, if you want to read file information from fs.stat, this code will be reused because the callback refers to the outer variable, which will need to be extracted from the outer layer.
2. The stack information is disconnected
As we know, the JavaScript engine maintains a stack of execution contexts that are created and pushed onto the stack when a function executes, and then pushed off the stack when the function completes execution.
If function B is called from function A, JavaScript pushes the execution context of function A onto the stack, then pushes the execution context of function B onto the stack, and then pushes the execution context of function A off the stack when function A is finished.
The advantage of this is that if we interrupt code execution, we can retrieve the entire stack and get whatever information we want from it.
Asynchronous callback function is not the case, however, such as the execution of fs, readdir, actually is to add a callback function to task queue, continue to execute code, until after completion of the main thread, can choose the task has been completed, from the task queue and add it to the stack and the stack, only that an execution context, if the correction error, There is no way to get information on the stack where the asynchronous operation was called, and it is not easy to determine where an error occurred.
Also, because it is asynchronous, you cannot catch errors directly with a try catch statement.
(Promise didn’t fix that, though.)
3. Use outer variables
When multiple asynchronous computations are performed simultaneously, as in this case, the order in which the file is read through is unpredictable, variables in the outer scope must be used, such as count, ERrored, stats, etc., which is not only difficult to write, but also if you ignore the file read error and do not record the error status. It will then read other files, causing unnecessary waste. In addition, outer variables may also be accessed and modified by other functions in the same scope, which is prone to misoperation.
The reason I mention callback hell alone is to say that nesting and indentation are just a shorthand for callback hell, and the problems it causes go beyond the readability of nesting.
Promise
Promise solved most of these problems.
1. Nesting problem
Here’s an example:
request(url, function(err, res, body) {
if (err) handleError(err);
fs.writeFile('1.txt', body, function(err) {
request(url2, function(err, res, body) {
if (err) handleError(err)
})
})
});
Copy the code
After using Promise:
request(url)
.then(function(result) {
return writeFileAsynv('1.txt', result)
})
.then(function(result) {
return request(url2)
})
.catch(function(e){
handleError(e)
});
Copy the code
For the example of reading the largest file, we use promise to simplify:
var fs = require('fs');
var path = require('path');
var readDir = function(dir) {
return new Promise(function(resolve, reject) {
fs.readdir(dir, function(err, files) {
if (err) reject(err);
resolve(files)
})
})
}
var stat = function(path) {
return new Promise(function(resolve, reject) {
fs.stat(path, function(err, stat) {
if (err) reject(err)
resolve(stat)
})
})
}
function findLargest(dir) {
return readDir(dir)
.then(function(files) {
let promises = files.map(file= > stat(path.join(dir, file)))
return Promise.all(promises).then(function(stats) {
return { stats, files }
})
})
.then(data= > {
let largest = data.stats
.filter(function(stat) { return stat.isFile() })
.reduce((prev, next) = > {
if (prev.size > next.size) return prev
return next
})
return data.files[data.stats.indexOf(largest)]
})
}
Copy the code
2. Reverse control and reverse control
When we talked about using third-party callback apis, we might encounter the following problems:
- The callback function is executed multiple times
- The callback function did not execute
- Callbacks are sometimes executed synchronously and sometimes asynchronously
For the first problem, the Promise can only resolve once, and the rest of the calls are ignored.
For the second problem, we can solve it using the promise.race function:
function timeoutPromise(delay) {
return new Promise( function(resolve,reject){
setTimeout( function(){
reject( "Timeout!"); }, delay ); }); }Promise.race( [
foo(),
timeoutPromise( 3000 )
] )
.then(function(){}, function(err){});
Copy the code
And the third question, why do you do it synchronously sometimes and asynchronously sometimes?
Let’s look at an example:
varcache = {... };function downloadFile(url) {
if(cache.has(url)) {
// If cache exists, this is synchronous call
return Promise.resolve(cache.get(url));
}
return fetch(url).then(file= > cache.set(url, file)); // This is an asynchronous call
}
console.log('1');
getValue.then((a)= > console.log('2'));
console.log('3');
Copy the code
In this example, it prints 1, 2, 3 with cahCE, and 1, 3, 2 with no cache.
However, if the synchronous and asynchronous code is used as an internal implementation, only the interface is exposed to the external call, the caller can not determine whether it is asynchronous or synchronous state, affecting the maintainability and testability of the program.
Simply put, synchronous and asynchronous coexistence does not guarantee consistency of program logic.
Promise solves this problem, though. Here’s an example:
var promise = new Promise(function (resolve){
resolve();
console.log(1);
});
promise.then(function(){
console.log(2);
});
console.log(3);
/ / 1 2 3
Copy the code
Even if the Promise object goes into the Resolved state immediately, calling the resolve function synchronously, the methods specified in the then function will still be done asynchronously.
The PromiseA+ specification also specifies:
In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously and should be executed in a new execution stack after the event loop in which the THEN method is called.
Promise anti-patterns
1. Promise nesting
// bad
loadSomething().then(function(something) {
loadAnotherthing().then(function(another) {
DoSomethingOnThem(something, another);
});
});
Copy the code
// good
Promise.all([loadSomething(), loadAnotherthing()])
.then(function ([something, another]) { DoSomethingOnThem(... [something, another]); });Copy the code
2. Broken Promise chains
// bad
function anAsyncCall() {
var promise = doSomethingAsync();
promise.then(function() {
somethingComplicated();
});
return promise;
}
Copy the code
// good
function anAsyncCall() {
var promise = doSomethingAsync();
return promise.then(function() {
somethingComplicated()
});
}
Copy the code
3. A chaotic collection
// bad
function workMyCollection(arr) {
var resultArr = [];
function _recursive(idx) {
if (idx >= resultArr.length) return resultArr;
return doSomethingAsync(arr[idx]).then(function(res) {
resultArr.push(res);
return _recursive(idx + 1);
});
}
return _recursive(0);
}
Copy the code
You can write it as:
function workMyCollection(arr) {
return Promise.all(arr.map(function(item) {
return doSomethingAsync(item);
}));
}
Copy the code
If you must execute in a queue, you can write:
function workMyCollection(arr) {
return arr.reduce(function(promise, item) {
return promise.then(function(result) {
return doSomethingAsyncWithResult(item, result);
});
}, Promise.resolve());
}
Copy the code
4.catch
// bad
somethingAync.then(function() {
return somethingElseAsync();
}, function(err) {
handleMyError(err);
});
Copy the code
If somethingElseAsync throws an error, it cannot be caught. You can write it as:
// good
somethingAsync
.then(function() {
return somethingElseAsync()
})
.then(null.function(err) {
handleMyError(err);
});
Copy the code
// good
somethingAsync()
.then(function() {
return somethingElseAsync();
})
.catch(function(err) {
handleMyError(err);
});
Copy the code
Traffic light problem
Title: red light once every three seconds, green light once every second, yellow light once every two seconds; How do I make three lights turn on again and again? (Implemented with Promse)
Three lighting functions already exist:
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
Copy the code
Using THEN and recursion:
function red(){
console.log('red');
}
function green(){
console.log('green');
}
function yellow(){
console.log('yellow');
}
var light = function(timmer, cb){
return new Promise(function(resolve, reject) {
setTimeout(function() {
cb();
resolve();
}, timmer);
});
};
var step = function() {
Promise.resolve().then(function(){
return light(3000, red);
}).then(function(){
return light(2000, green);
}).then(function(){
return light(1000, yellow);
}).then(function(){
step();
});
}
step();
Copy the code
promisify
Sometimes we need to transform the API of the callback syntax into the Promise syntax, for which we need a promisify method.
The last argument is passed to the callback function, and the first argument is an error message (null if there is no error), so we can write a simple method called promisify:
function promisify(original) {
return function (. args) {
return new Promise((resolve, reject) = > {
args.push(function callback(err, ... values) {
if (err) {
return reject(err);
}
returnresolve(... values) }); original.call(this. args); }); }; }Copy the code
See ES6-PromisIf for complete information
Limitations of Promises
1. Get eaten by mistake
So the first thing we need to understand is, what is error eaten, does it mean that the error message is not printed?
No, here’s an example:
throw new Error('error');
console.log(233333);
Copy the code
In this case, because of throw error, the code is blocked and 233333 is not printed. Here’s another example:
const promise = new Promise(null);
console.log(233333);
Copy the code
The above code still blocks execution because if a Promise is used in an invalid way and an error blocks the normal Promise construction, the result is an exception that runs out immediately, rather than a rejected Promise.
But here’s another example:
let promise = new Promise((a)= > {
throw new Error('error')});console.log(2333333);
Copy the code
This time, 233333 will be printed as normal, indicating that errors inside the Promise will not affect the code outside the Promise, which is often referred to as “error eating.”
This isn’t the only limitation of promises, try.. The same goes for catch, which also catches an exception and simply eats the error.
Because errors are eaten, errors in the Promise chain are easy to ignore, which is why it is generally recommended to add a catch function at the end of the Promise chain, because for a Promise chain without an error handler, any errors will be propagated through the chain. Until you register your error handlers.
2. A single value
Promise can only have one completion value or one reason for rejection. However, in real use, multiple values are often needed to be transferred. The general approach is to construct an object or array, and then pass, and after obtaining this value in THEN, the operation of value assignment will be conducted.
To be honest, there is no good way to do this. The suggestion is to use ES6’s deconstructive assignment:
Promise.all([Promise.resolve(1), Promise.resolve(2)])
.then(([x, y]) = > {
console.log(x, y);
});
Copy the code
3. Unable to cancel
Once a Promise is created, it executes immediately and cannot be canceled.
4. The pending status cannot be known
When you are in a pending state, you have no way of knowing what stage of progress you are currently in (just started or about to complete).
reference
- JavaScript You Didn’t Know By Volume
- N uses of Promise
- JavaScript Promise mini-book
- Promises/A + specification
- How Promise is used
- Promise Anti-patterns
- An interview question about the Promise application
ES6 series
ES6 directory address: github.com/mqyqingfeng…
ES6 series is expected to write about 20 chapters, aiming to deepen the understanding of ES6 knowledge points, focusing on the block-level scope, tag template, arrow function, Symbol, Set, Map and Promise simulation implementation, module loading scheme, asynchronous processing and other contents.
If there is any mistake or not precise place, please be sure to give correction, thank you very much. If you like or are inspired by it, welcome star and encourage the author.