May 08, 2021 ES6
Promise
is
异步编程
programming that is more reasonable and powerful than traditional solutions, callback functions and events.
It was first proposed and implemented by the community, and ES6 wrote it into language standards, unified usage, and natively provided the Promise object.
Promise, in short, is a container
容器
the results of an event (usually an asynchronous operation) that will end in the future.
Promise
G
rammatically, Promise is an object from which to get messages for asynchronous operations.
Promise provides a unified API that can be handled in the same way for a variety of asynchronous operations.
The Promise object has two characteristics.
(1) The state of the object is not affected by the outside world. T
he Promise object represents an asynchronous operation with three states:
pending
(in action),
fulfilled
(successful), and
rejected
(failed). O
nly the result of an asynchronous operation can determine which state it is currently in, and no other action can change that state.
This is also the name Promise, which means "commitment" in English, meaning that other means cannot be changed.
(2) Once the state changes, it will not change, and this result can be obtained at any time. T he state of the Promise object changes, with only two possibilities: from pending to fulfilled and from pending to rejected. A s long as these two conditions occur, the state solidifies, does not change, and the result is maintained, which is then called resolved (stereotyped). I f the change has already occurred, you add a callback function to the Promise object and you get the result immediately. This is completely different from events, which are characterized by the fact that if you miss it and then listen, you don't get results.
Note that, for the convenience of the text, the resolved unity later in this chapter refers only to the fulfilled state and does not contain the rejected state.
With the Promise object, asynchronous operations can be expressed to synchronize the flow of the operation, avoiding layered callback functions. In addition, the Promise object provides a unified interface that makes it easier to control asynchronous operations.
Promise also has some drawbacks. F irst, You can't cancel Promise, you can't cancel it as soon as you create it, you can't cancel it halfway. S econd, if you do not set the callback function, the error thrown inside Promise will not be reflected externally. Third, when you are in the pending state, you have no way of knowing where you are going (just starting or about to finish).
If certain events are repeated over and over again, in general, using Stream mode is a better option than deploying Promise.
ES6 states that
Promise
object is a
构造函数
is used to generate a
Promise
instance.
The following code creates a Promise instance.
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
The Promise constructor accepts a function as an argument, and the two arguments of the function are
resolve
and
reject
They are two functions, provided by the JavaScript engine, without having to deploy them themselves.
resolve
of
Promise
function is to change the state of the Promise object from "unfinished" to
“未完成”
(i.e.,
Promise
from pending
“失败”
to
“成功”
to call when the asynchronous operation succeeds, and to pass the
“未完成”
the asynchronous operation as an argument;
reject
After the Promise instance is generated, you
then
resolved
and
rejected
respectively, using the then method.
promise.then(function(value) {
// success
}, function(error) {
// failure
});
The then method can accept two callback functions as arguments. T
he first callback function is called when the state of the Promise object changes to
resolved
and the second callback function
Promise
of the Promise object changes to
Promise
rejected
T
he second function is optional and does not have to be provided.
Both functions accept values that come out of the Promise object as arguments.
Here is a simple example of a Promise object.
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});
In the above code, the timeout method returns a Promise instance that represents results that will not occur for some time. After the specified time (ms parameter), the state of the Promise instance changes to resolved, triggering the callback function bound by the then method.
Promise is executed as soon as it is created.
let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved
In the code above, Promise executes as soon as it is created, so the first output is Promise. Then, the callback function specified by the then method will not execute until all synchronization tasks in the current script have been executed, so the resolved final output.
The following is an example of an asynchronous loading of a picture.
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
In the code above, an asynchronous action of loading a picture is wrapped with Promise. If the load is successful, the resolve method is called, otherwise the reject method is called.
The following is an example of an Ajax operation implemented with a Promise object.
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
In the above code, getJSON encapsulates the XMLHttpRequest object, makes an HTTP request for JSON data, and returns a Promise object. It is important to note that inside getJSON, both the resolve function and the reject function are called with parameters.
If you
resolve
function and the
reject
function with arguments, their arguments are passed to
回调函数
reject
to the reject function is
Error
an instance of the Error
Promise
resolve
which represents an error thrown;
const p1 = new Promise(function (resolve, reject) {
// ...
});
const p2 = new Promise(function (resolve, reject) {
// ...
resolve(p1);
})
In the above code, p1 and p2 are instances of Promise, but the resolve method of p2 treats p1 as an argument, i.e. the result of one asynchronous operation is to return another asynchronous operation.
Note that the state of p1 is passed to p2, that is, the state of p1 determines the state of p2. If the state of p1 is pending, the callback function of p2 waits for the state of p1 to change, and if the state of p1 is already resolved or rejected, the callback function of p2 is executed immediately.
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail
In the code above, p1 is a Promise, which changes to rejected after 3 seconds. T he state of p2 changes after 1 second, and the resolve method returns p1. B ecause p2 returns another Promise, the state of p2 itself is invalid, and the state of p2 is determined by the state of p1. T herefore, the later then statements become for the latter (p1). After another 2 seconds, p1 becomes rejected, causing the callback function specified by the catch method to be triggered.
Note that calling resolve or reject does not end the execution of Promise's argument function.
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then(r => {
console.log(r);
});
// 2
// 1
In the above code, after calling resolve(1), the console .log (2) is executed and printed first. This is because Promise, which is immediately resolved, is performed at the end of the current cycle of events, always later than the synchronization task of the current cycle.
In general, after calling resolve or reject, Promise's mission is complete, and the successor should be placed in the then method, not directly after resolve or reject. Therefore, it is a good idea to precede them with a return statement so that there are no surprises.
new Promise((resolve, reject) => {
return resolve(1);
// 后面的语句不会执行
console.log(2);
})
Promise
instance has
then
method, that is, the then method is defined on the prototype
Promise.prototype
I
ts role is to add callback functions for Promise instances when the state changes.
As mentioned earlier, the first argument of the then method is the callback function of the resolved state, and the second argument (optional) is the callback function of the rejected state.
The then method returns
新的
new Promise instance (note that it is not the original Promise instance).
Therefore,
链式
a chained approach, that is, the then method is followed by another then method.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});
The code above uses
then
method and specifies two callback functions in turn.
When the first callback function is complete, the return result is passed in to the second callback function as an argument.
With chained then, you can specify a set of callback functions that are called in order. At this point, the previous callback function, it is possible to return a Promise object (that is, there is an asynchronous operation), then the 3d callback function, will wait for the state of the Promise object to change before being called.
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function (comments) {
console.log("resolved: ", comments);
}, function (err){
console.log("rejected: ", err);
});
In the code above, the callback function specified by the first then method returns another Promise object. A t this point, the callback function specified by the second then method waits for the state of the new Promise object to change. If it becomes resolved, the first callback function is called, and if the state becomes rejected, the second callback function is called.
With the arrow function, the above code can be written more concisely.
getJSON("/post/1.json").then(
post => getJSON(post.commentURL)
).then(
comments => console.log("resolved: ", comments),
err => console.log("rejected: ", err)
);
Promise.prototype.catch()
.then(null, rejection)
or
.then(undefined, rejection)
to specify a callback function in the event of an error.
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
In the above code, the getJSON() method returns a Promise object that, if the state of the object changes to resolved, calls the callback function specified by the then() method, and if an asynchronous operation throws an error, the state changes to rejected, and the callback function specified by the catch() method is called to handle the error. In addition, the callback function specified by the then() method is also caught by the catch() method if an error is thrown during the run.
p.then((val) => console.log('fulfilled:', val))
.catch((err) => console.log('rejected', err));
// 等同于
p.then((val) => console.log('fulfilled:', val))
.then(null, (err) => console.log("rejected:", err));
Here's an example.
const promise = new Promise(function(resolve, reject) {
throw new Error('test');
});
promise.catch(function(error) {
console.log(error);
});
// Error: test
In the code above, promise throws an error and is caught by the callback function specified by the catch() method. Note that the above writing is equivalent to the following two.
// 写法一
const promise = new Promise(function(resolve, reject) {
try {
throw new Error('test');
} catch(e) {
reject(e);
}
});
promise.catch(function(error) {
console.log(error);
});
// 写法二
const promise = new Promise(function(resolve, reject) {
reject(new Error('test'));
});
promise.catch(function(error) {
console.log(error);
});
By comparing the above two writings, you can see that the reject() method does the same as throwing an error.
If the Promise state has become resolved, it is not valid to throw an error again.
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise
.then(function(value) { console.log(value) })
.catch(function(error) { console.log(error) });
// ok
In the above code, Promise is followed by a resolve statement and then throws an error that is not caught, which is equivalent to not throwing. Because once the state of Promise changes, it remains in that state forever and will not change again.
Errors in the Promise object are "bubbling" and are passed back until caught. That is, the error is always caught by the next catch statement.
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
In the above code, there are three Promise objects: one produced by getJSON() and two by then(). Any error thrown by any of them is caught by the last catch().
In general, do not define the callback function of the Reject state (that is, the second argument to that) in the then() method, always using the catch method.
// bad
promise
.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise
.then(function(data) { //cb
// success
})
.catch(function(err) {
// error
});
In the above code, the second type of writing is better than the first, on the grounds that the second type of writing captures errors in the execution of the previous then method and is closer to synchronous writing (try/catch). Therefore, it is recommended that you always use the catch() method instead of the second argument of the then() method.
Unlike traditional try/catch blocks of code, if you do not specify a callback function for error handling using the catch() method, the error thrown by the Promise object is not passed to the outer layer of code, i.e. there is no response.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
In the code above, the Promise object generated by the someAsyncThing() function has an internal syntax error. W hen the browser runs to this line, it prints an error message that says ReferenceError: x is not defined, but does not exit the process, terminates script execution, and outputs 123 after 2 seconds. That said, errors inside Promise don't affect the code outside Of Promise, and the popular saying is that "Promise eats the error."
The script is executed on the server, and the exit code is 0 (that is, the execution was successful). However, node.js has a unhandledRejection event that listens specifically for uncaught reject errors, and the script above triggers the listening function for this event, which can be thrown inside the listening function.
process.on('unhandledRejection', function (err, p) {
throw err;
});
In the code above, the listening function for the unhandledRejection event has two parameters, the first is the error object and the second is the error-reported Promise instance, which can be used to understand the environment in which the error occurred.
Note that Node has plans to abolish the unhandled Rejection event in the future. If there is an uncaught error inside Promise, the process is terminated directly and the process exit code is not 0.
Let's look at the example below.
const promise = new Promise(function (resolve, reject) {
resolve('ok');
setTimeout(function () { throw new Error('test') }, 0)
});
promise.then(function (value) { console.log(value) });
// ok
// Uncaught Error: test
In the code above, Promise specifies that the error is thrown again in the next "event loop". By that time, Promise's run was over, so the error was thrown outside the Promise function and bubbled to the outerst layer, making it an uncaught error.
It is generally recommended that the Promise object be followed by the catch() method, which can handle errors that occur within Promise. The catch() method returns a Promise object, so you can then call the then() method later.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
// carry on
The above code runs the callback function specified by the catch() method, and then runs the callback function specified by the later then() method. If no errors are reported, the catch() method is skipped.
Promise.resolve()
.catch(function(error) {
console.log('oh no', error);
})
.then(function() {
console.log('carry on');
});
// carry on
Because the above code does not report an error, skips the catch() method and executes the later then() method directly. At this point, if an error is reported in the then() method, it has nothing to do with catch() earlier.
catch() method, you can also throw errors.
const someAsyncThing = function() {
return new Promise(function(resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2);
});
};
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为 y 没有声明
y + 2;
}).then(function() {
console.log('carry on');
});
// oh no [ReferenceError: x is not defined]
In the above code, the catch() method throws an error because there is no other catch() method behind it, so the error is not caught and is not passed to the outer layer. If you rewrite it, the results will be different.
someAsyncThing().then(function() {
return someOtherAsyncThing();
}).catch(function(error) {
console.log('oh no', error);
// 下面一行会报错,因为y没有声明
y + 2;
}).catch(function(error) {
console.log('carry on', error);
});
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]
In the code above, the second catch() method is used to catch errors thrown by the previous catch() method.
finally()
specifies the actions
Promise
regardless of the last state of the Promise object.
This method is
ES2018
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
In the above code, regardless of the last state of the promise, the callback function specified by the final method is executed after the callback function specified by then or catch is executed.
Here's an example of a server that uses Promise to process requests and then uses the final method to shut down the server.
server.listen(port)
.then(function () {
// ...
})
.finally(server.stop);
The callback function of the final method does not accept any arguments, which means that there is no way to know whether the previous Promise state is fulfilled or rejected. This indicates that the operations in the final method should be state independent and not dependent on the results of Promise's execution.
final is essentially a special case of the then method.
promise
.finally(() => {
// 语句
});
// 等同于
promise
.then(
result => {
// 语句
return result;
},
error => {
// 语句
throw error;
}
);
In the above code, if you do not use the final method, the same statement needs to be written once for both success and failure. With the final method, you only need to write it once.
Its implementation is also very simple.
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
In the code above, the callback function callback is executed regardless of whether the previous Promise is fulfilled or rejected.
As you can also see from the above implementation, the final method always returns the original value.
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})
// reject 的值是 3
Promise.reject(3).finally(() => {})
Promise.all()
method is used to
Promise
instances into a new Promise instance.
const p = Promise.all([p1, p2, p3]);
In the above code, the Promise.all() method accepts an array as an argument, p1, p2, p3 are all Promise instances, and if not, the Promise.resolve method described below is called first, the parameters are converted to a Promise instance, and then processed further. In addition, the parameters of the Promise.all() method can be not arrays, but must have an Iterator interface, and each member returned is a Promise instance.
The status of p is determined by p1, p2, p3, divided into two cases.
(1) Only if the state of p1, p2, p3 becomes fulfilled, the state of p becomes fulfilled, at which point the return values of p1, p2, and p3 form an array of callback functions passed to p.
(2) As long as there is one in p1, p2, p3 that is rejected, the state of p becomes rejected, at which point the return value of the first instance of the reject is passed to the callback function of p.
Here is a specific example.
// 生成一个Promise对象的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return getJSON('/post/' + id + ".json");
});
Promise.all(promises).then(function (posts) {
// ...
}).catch(function(reason){
// ...
});
In the code above, promises are an array of six Promise instances that call the callback function after the Promise.all method only if the state of all six instances becomes fulfilled, or one of them becomes rejected.
Here's another example.
const databasePromise = connectDatabase();
const booksPromise = databasePromise
.then(findAllBooks);
const userPromise = databasePromise
.then(getCurrentUser);
Promise.all([
booksPromise,
userPromise
])
.then(([books, user]) => pickTopRecommendations(books, user));
In the above code, booksPromise and userPromise are two asynchronous operations that trigger the pickTopRecommendations callback function only when their results are returned.
Note that if you define the catch method yourself as a Promise instance of an argument, it does not trigger the catch method of Promise.all() once it is rejected.
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]
In the above code, p1 is resolved, p2 is rejected first, but p2 has its own catch method, which returns a new Promise instance, and p2 actually points to that instance. After the instance executes the catch method, it also becomes resolved, causing both instances within the Promise.all() method parameter to be resolved, so the callback function specified by the then method is called between the callback functions specified by the catch method and not the callback functions specified by the catch method.
If p2 does not have its own catch method, the catch method of Promise.all() is called.
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了
Promise.race()
method also wraps multiple Promise instances into a new Promise instance.
const p = Promise.race([p1, p2, p3]);
In the above code, as long as one instance in p1, p2, and p3 changes the state first, the state of p changes. The return value of the First Changed Promise instance is passed to the callback function of p.
The parameters of the Promise.race() method are the same as between the Promise.all() method, and if it is not a Promise instance, the Promise.resolve() method described below is called first, the parameters are converted to a Promise instance, and then processed further.
Here's an example of how to change the state of Promise to reject if you don't get results within the specified time, or you change to resolve.
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);
In the above code, if the fetch method cannot return the result within 5 seconds, the state of variable p becomes rejected, triggering the callback function specified by the catch method.
Promise.allSettled()
accepts a set of
Promise
instances as parameters and wraps them into a new Promise instance. T
he wrapper instance does not end until all of these parameter instances return results, whether fulfilled or rejected.
This method was
introduced by ES2020.
const promises = [
fetch('/api-1'),
fetch('/api-2'),
fetch('/api-3'),
];
await Promise.allSettled(promises);
removeLoadingIndicator();
The code above makes three requests to the server, and when all three requests are completed, the loaded scroll icon disappears regardless of whether the request succeeds or fails.
The method returns a new Promise instance that, once completed, is always fulfilled and does not become rejected. When the state becomes fulfilled, Promise's listening function receives an array of parameters, each member corresponding to an instance of Promise that is passed in to Promise.allSettled().
const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
console.log(results);
});
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]
In the code above, the return value of Promise.allSettled() is allSettledPromise, and the state can only become fulfilled. I ts listening function receives an array of results. E ach member of the array is an object that corresponds to two Promise instances passed in to Promise.allSettled(). E ach object has a status property, the value of which can only be a string fulfilled or a string rejected. When fulfilled, the object has a value property, and when rejected, there is a reason property, which corresponds to the return value of both states.
The following is an example of return value usage.
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.allSettled(promises);
// 过滤出成功的请求
const successfulPromises = results.filter(p => p.status === 'fulfilled');
// 过滤出失败的请求,并输出原因
const errors = results
.filter(p => p.status === 'rejected')
.map(p => p.reason);
Sometimes, we don't care about the results of asynchronous operations, we just care if they end. A t this point, the Promise.allSettled() method is useful. W ithout this approach, it can be cumbersome to make sure that everything is over. The Promise.all() method does not do this.
const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x));
try {
await Promise.all(requests);
console.log('所有请求都成功。');
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。');
}
In the code above, Promise.all() cannot be sure that all requests are closed. To do this, it's cumbersome to write, and with Promise.allSettled(), it's easy.
Promise.any()
accepts a set of Promise instances as parameters and wraps them as a new Promise instance. A
s long as one of the parameter instances becomes a fulfilled state, the wrapper instance becomes a fulfilled state, and if all parameter instances become rejected, the wrapper instance becomes a rejected state.
The approach is currently a phase III
proposal.
Promise.any() is very similar to the Promise.race() approach, with only one difference: it doesn't end because a Promise becomes a rejected state.
const promises = [
fetch('/endpoint-a').then(() => 'a'),
fetch('/endpoint-b').then(() => 'b'),
fetch('/endpoint-c').then(() => 'c'),
];
try {
const first = await Promise.any(promises);
console.log(first);
} catch (error) {
console.log(error);
}
In the code above, the array of parameters for the Promise.any() method contains three Promise operations. A s soon as one of them becomes fulfilled, the Promise object returned by Promise.any() becomes fulfilled. If all three operations become rejected, the await command throws an error.
Promise.any() throws an error that is not a general error, but an AggregateError instance. I t is equivalent to an array, with each member corresponding to an error thrown by the rejected operation. The following is an example of the implementation of AggregateError.
new AggregateError() extends Array -> AggregateError
const err = new AggregateError();
err.push(new Error("first error"));
err.push(new Error("second error"));
throw err;
When catching errors, if you don't use try... Catch structure and await command, you can write like this.
Promise.any(promises).then(
(first) => {
// Any of the promises was fulfilled.
},
(error) => {
// All of the promises were rejected.
}
);
Here's an example.
var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var alsoRejected = Promise.reject(Infinity);
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result); // 42
});
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results); // [-1, Infinity]
});
Sometimes you need to turn an existing object into a
Promise.resolve()
approach does just that.
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
The above code turns the deferred object generated by jQuery into a new Promise object.
Promise.resolve() is equivalent to the following writing.
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
The parameters of the Promise.resolve method are divided into four cases.
(1) The parameter is a Promise instance
If the parameter is a Promise instance, Promise.resolve will return the instance intact without any modifications.
(2) The parameter is a thenable object
A thenable object is an object that has a then method, such as the following.
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
The Promise.resolve method turns the object into a Promise object, and then immediately executes the then method of the thenable object.
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
In the above code, after the then method of the thenable object is executed, the state of the object p1 becomes resolved, which immediately executes the callback function specified by the last then method, output 42.
(3) The parameter is not an object with a then method, or is not an object at all
If the argument is an original value, or if it is an object that does not have a then method, the Promise.resolve method returns a new Promise object with a status of resolved.
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
The above code generates an instance p of a new Promise object. B ecause the string Hello is not an asynchronous operation (the method is determined that the string object does not have a then method), the callback function executes immediately when the state that returns the Promise instance is resolved from a generation. Arguments to the Promise.resolve method are passed to the callback function at the same time.
(4) Without any parameters
The Promise.resolve() method allows a Promise object in the resolved state to be returned directly without parameters when called.
So, if you want a Promise object, it's convenient to call the Promise.resolve() method directly.
const p = Promise.resolve();
p.then(function () {
// ...
});
The variable p in the code above is a Promise object.
It is important to note that the Promise object for immediate resolve() is executed at the end of the current event loop, not at the beginning of the next "event loop".
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
In the above code, setTimeout (fn, 0) is executed at the beginning of the next "event loop", Promise.resolve() is executed at the end of the current "event loop", and console.log ('one') is executed immediately, so output first.
Promise.reject(reason)
returns a new Promise instance with a status of
rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
The above code generates an instance p of the Promise object, with a status of rejected, and the callback function executes immediately.
Note that the parameters of the Promise.reject() method become the parameters of subsequent methods as the reason for the reject. This is not consistent with the Promise.resolve approach.
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
In the above code, the parameters of the Promise.reject method are a thenable object, and after execution, the parameters of the catch method that follow are not the "error" string thrown by reject, but the thenable object.
We can write the loading of the picture as a Promise, and once the load is complete, the status of Promise changes.
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};
Using
Generator
function
流程
the process, a Promise object is typically returned when an asynchronous operation is encountered.
function getFoo () {
return new Promise(function (resolve, reject){
resolve('foo');
});
}
const g = function* () {
try {
const foo = yield getFoo();
console.log(foo);
} catch (e) {
console.log(e);
}
};
function run (generator) {
const it = generator();
function go(result) {
if (result.done) return result.value;
return result.value.then(function (value) {
return go(it.next(value));
}, function (error) {
return go(it.throw(error));
});
}
go(it.next());
}
run(g);
In generator function g of the code above, there is an asynchronous operation getFoo that returns a Promise object. The function run is used to handle the Promise object and call the next next method.
In real-world development, there is often a situation where you don't know or don't want to tell, whether function f is a synchronization function or an asynchronous operation, but you want to handle it with Promise. B ecause this allows you to specify the next process with the then method regardless of whether f contains asynchronous operations or not, and handle the error thrown by f with the catch method. The following writing is generally used.
Promise.resolve().then(f)
One drawback of the above writing is that if f is a synchronization function, it will execute at the end of the current cycle of events.
const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now
In the code above, function f is synchronized, but when wrapped with Promise, it becomes asynchronous.
So is there a way for synchronization functions to execute synchronously, asynchronous functions to execute asynchronously, and have them have a unified API? T he answer is yes, and there are two ways to write it. The first is to write with the async function.
const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next
In the above code, the second line is an anonymous function that executes immediately, and the async function inside is executed immediately, so if f is synchronized, the result of synchronization is obtained, and if f is asynchronous, you can specify the next step with then, as you write below.
(async () => f())()
.then(...)
It is important to note that async() will eat the error thrown by f(). So, if you want to catch an error, use the promise.catch method.
(async () => f())()
.then(...)
.catch(...)
The second way to write is to use new Promise().
const f = () => console.log('now');
(
() => new Promise(
resolve => resolve(f())
)
)();
console.log('next');
// now
// next
The above code also executes new Promise() using an anonymous function that executes immediately. In this case, the synchronization function is also executed synchronously.
Since this is a very common requirement, there is now a proposal to provide the Promise.try method instead of the above writing.
const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next
In fact, Promise.try has been around for a long time, and the Promise Library Bluebird, Q, and when have long provided this approach.
Because Promise.try provides a unified processing mechanism for all operations, it's best to wrap it up with Promise.try if you want to manage the process with the then method. This has many benefits, one of which is that exceptions can be better managed.
function getUsername(userId) {
return database.users.get({id: userId})
.then(function(user) {
return user.name;
});
}
In the above code, database.users.get() returns a Promise object that can be captured using the catch method if an asynchronous error is thrown, as written below.
database.users.get({id: userId})
.then(...)
.catch(...)
But database.users.get() may also throw synchronization errors (such as database connection errors, depending on how you implement them), and you'll have to use try... catch to catch.
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
The above writing is clumsy, and you can capture all sync and asynchronous errors with promise.catch().
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)
In fact, Promise.try simulates try blocks of code, just as promise.catch simulates catch blocks of code.