理解Promise in JavaScript
Promise
是JavaScript中的一个核心概念,初学JavaScript,对Promise
的概念和用法都比较模糊,这里做一个总结。
首先Promise是用来执行异步代码的,用来替代回调函数。在讲回调函数之前,可以先看下什么是同步操作,什么是异步操作。
同步操作
正常代码是同步执行的,执行一条命令需要等它完成之后才会执行下一条命令。例如读取一个文件的内容,
// Node.js program to demonstrate the
// fs.readFileSync() method
// Include fs module
const fs = require('fs');
// Calling the readFileSync() method
// to read 'input.txt' file
const data = fs.readFileSync('./input.txt',
{encoding:'utf8', flag:'r'});
// Display the file data
console.log(data);
执行完之后立刻就可以知道文件的内容,但这种方式耗时比较久,在等待执行完成的过程时候,不能执行其他操作,这样就很容易阻塞到UI线程。
异步操作
与同步方式不同,异步操作在调用某个函数后立刻返回,同时使用callback函数,在异步操作完成的时候自动调用。读文件用异步来操作可以表示为:
readFile("./input.txt", (error, result) => {
// This callback will be called when the task is done, with the
// final `error` or `result`. Any operation dependent on the
// result must be defined within this callback.
console.log(result);
});
// Code here is immediately executed after the `readFile` request
// is fired. It does not wait for the callback to be called, hence
// making `readFile` "asynchronous".
这种异步操作有两个缺点,一个是多层的callback会比较晦涩难懂,不容易维护;第二个是出错处理很麻烦,因为实际在执行回调函数的时候已经没有之前同步执行的堆栈,没有办法回溯到可以处理异常的代码块。
Promise
Promise
用来表示一次异步操作的未来结果,既然是表示未来的结果,那就需要通过这个Promise
可以知道异步操作是否成功,如果成功,返回值是什么?如果失败,失败的异常是什么。同时因为是异步操作,Promise
可以注册回调函数,需要注意的是,注册的回调函数最多只能被调用一次;
创建Promise
- 使用
Promise
的构造函数来创建,用法如下:
创建一个Promise
的语法是
new Promise(executor)
其中executor
是一个函数,接受两个参数resolve
和reject
。resolve
和reject
都是函数,并且接受一个任意类型的输入参数。
其中resolve
函数解决或兑现返回的Promise
,或者调用reject
函数拒绝返回的Promise
.
上面这个例子中的执行过程如下:
-
new Promise
会构造一个Promise
对象,同时会产生两个函数对象,分别是resolve
和reject
,这两个函数对象和这个Promise
对象绑定在一起。 -
new Promise
的参数是一个函数executor
,这个函数用来封装一些操作,这些操作会通过异步的方式执行。同时这个函数接受两参数,分别是resolve
和reject
.executor
在创建Promise
后立刻执行,同时把resolve
和reject
的对象作为参数。 - 这个
Promise
的状态是通过调用resolve
和reject
的调用来改变的。
- 如果
resolve
先被调用,那么Promise
就被解决或者兑现,同时传给resovle
的参数会被认为Promise
对象对应的结果。 - 如果
reject
先被调用,那么Promise
则被拒绝,同时传给reject
的参数也会被认为是Promise
对象对应的异常。
以下例子来源于MDN:
const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR or an HTML API.
setTimeout(() => {
resolve("Success!"); // Yay! Everything went well!
}, 250);
});
myFirstPromise.then((successMessage) => {
// successMessage is whatever we passed in the resolve(...) function above.
// It doesn't have to be a string, but if it is only a succeed message, it probably will be.
console.log(`Yay! ${successMessage}`);
});
以上例子中就是通过resolve
来把"Success"信息传给通过myFirstPromise.then
注册的回调函数,同时myFirstPromise
也会变为fulfill
的状态。
- 使用
then()
函数来创建
then()
可以创建并返回一个新的Promise
,例如
function getJSON(url){
return fetch(url).then(response => response.json());
}
fetch(url)
返回是一个Promise P1
,P1
对应的实际结果是一个Response
对象。Response
对象的 json()
方法返回一个新的Promise P2
,那么P1
被解决为P2
。当P2
兑现时(fulfill),P1
也会用相同的值来fulfill.
还是用刚才读文件的例子来说明,如果用Promise实现的话就是以下方式:
const readFilePromise = (path) =>
new Promise((resolve, reject) => {
readFile(path, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
readFilePromise("./input.txt")
.then((result) => console.log(result))
.catch((error) => console.error("Failed to read data"));
使用Promise
如果使用Promise异步,假设有一个函数readFilePromise
,那么上面的代码就变成:
readFilePromise("./input.txt").then(fileContent => {
// 该函数为callback函数,在文件内容被读出的时候调用,接受的参数为
// 文件的内容。注意该函数只会被调用一次。
console.log(fileContent);
});
注意readFilePromise
返回的是一个Promise对象,Promise
对象有then()
的方法,可以用来注册回调函数。该回调函数在Promise兑现后调用,调用的时候传入的参数为该Promise
的实际结果,在此处就是文件的实际内容;
如果读取文件失败,怎么处理:
readFilePromise("./input.txt").then(fileContent => {
// 该函数为callback函数,在文件内容被读出的时候调用,接受的参数为
// 文件的内容。注意该函数只会被调用一次。
console.log(fileContent);
}).catch(() => console.log("error"));
如果要等待Promise
的完成,可以使用
await yourPromise;
Promise的状态
一个Promise
可以有三个状态,分别是fulfill
,reject
和pending
。刚创立的时候,Promise
的状态为pending
,一旦被fulfill
或者reject
,则该Promise
为settle,且状态不会改变。
一个Promise
代表了异步操作的结果,如果异步代码正常结束,这个结果就是代码的正常返回值,同时这个结果会作为参数传递给then()的第一个参数注册的函数;如果代码执行异常,那这个结果就是一个Error对象或者某个其他值,这个结果会作为参数传递给catch()注册的或者then()的第二个参数注册的回调函数。
Promise Chain
先看一个例子:
fetch(theURL) // task 1, return Promise P1
.then(callback1) // task 2, return Promise P2
.then(callback2); // task 3, return Promise P3
callback1是当P1
兑现的时候调用,并且把P1
的结果作为参数传给callback1; callback1
必须返回一个新的Promise P2
,并把P2
兑现的结果作为输入参数送给callback2
。
举一个具体的例子:
function callback1(response){
let p4 = response.json();
return p4
}
function callback2(profile){
displayUserProfile(profile);
}
let p1 = fetch("/api/user/profile");
let p2 = p1.then(callback1);
let p3 = p2.then(callback2);
Reference
MDN Promise
参数结构
JavaScript权威指南第7版