摘要:在本教程中,您将了解 JavaScript Promise 及其有效使用方式。
为什么使用 JavaScript Promise
以下示例定义了一个函数getUsers()
,它返回一个用户对象列表
function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}
Code language: JavaScript (javascript)
每个用户对象都有两个属性:username
和 email
。
要从 getUsers()
函数返回的用户列表中按用户名查找用户,您可以使用 findUser()
函数,如下所示
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
Code language: JavaScript (javascript)
在 findUser()
函数中
- 首先,通过调用
getUsers()
函数获取用户数组 - 其次,使用
find()
方法在users
数组上查找具有特定username
的用户。 - 第三,返回匹配的用户。
以下是查找用户名为 'john'
的用户的完整代码
function getUsers() {
return [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
console.log(findUser('john'));
Code language: JavaScript (javascript)
输出
{ username: 'john', email: '[email protected]' }
Code language: CSS (css)
findUser()
函数中的代码是同步阻塞的。findUser()
函数执行 getUsers()
函数以获取用户数组,调用 users
数组上的 find()
方法搜索具有特定用户名的用户,并返回匹配的用户。
实际上,getUsers()
函数可能会访问数据库或调用 API 来获取用户列表。因此,getUsers()
函数将会有延迟。
为了模拟延迟,您可以使用 setTimeout()
函数。例如
function getUsers() {
let users = [];
// delay 1 second (1000ms)
setTimeout(() => {
users = [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}, 1000);
return users;
}
Code language: JavaScript (javascript)
工作原理。
- 首先,定义一个数组
users
并将其值初始化为空数组。 - 其次,在
setTimeout()
函数的回调中将用户的数组分配给users
变量。 - 第三,返回
users
数组
getUsers()
无法正常工作,始终返回空数组。因此,findUser()
函数无法按预期工作
function getUsers() {
let users = [];
setTimeout(() => {
users = [
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
];
}, 1000);
return users;
}
function findUser(username) {
const users = getUsers(); // A
const user = users.find((user) => user.username === username); // B
return user;
}
console.log(findUser('john'));
Code language: JavaScript (javascript)
输出
undefined
Code language: JavaScript (javascript)
因为 getUsers()
返回空数组,所以 users
数组为空(A 行)。在 users
数组上调用 find()
方法时,该方法返回 undefined
(B 行)
挑战在于如何在一秒钟后访问从 getUsers()
函数返回的 users
。一种经典方法是使用回调。
使用回调来处理异步操作
以下示例在 getUsers()
和 findUser()
函数中添加了回调参数
function getUsers(callback) {
setTimeout(() => {
callback([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
}
function findUser(username, callback) {
getUsers((users) => {
const user = users.find((user) => user.username === username);
callback(user);
});
}
findUser('john', console.log);
Code language: JavaScript (javascript)
输出
{ username: 'john', email: '[email protected]' }
Code language: CSS (css)
在本例中,getUsers()
函数接受回调函数作为参数,并在 setTimeout()
函数中使用 users
数组调用它。此外,findUser()
函数接受一个回调函数来处理匹配的用户。
回调方法非常有效。但是,它使代码更难理解。此外,它增加了带有回调参数的函数的复杂性。
如果函数数量增加,您可能会遇到回调地狱问题。为了解决这个问题,JavaScript 提出 Promise 的概念。
了解 JavaScript Promise
根据定义,Promise 是一个对象,它封装了异步操作的结果。
Promise 对象有一个状态,可以是以下状态之一
- 等待中
- 已完成,带有值
- 由于原因而被拒绝
在开始时,Promise 的状态是等待中,表示异步操作正在进行。根据异步操作的结果,状态将更改为已完成或拒绝。
已完成状态表示异步操作已成功完成
拒绝状态表示异步操作失败。
创建 Promise
要创建 Promise 对象,可以使用 Promise()
构造函数
const promise = new Promise((resolve, reject) => {
// contain an operation
// ...
// return the state
if (success) {
resolve(value);
} else {
reject(error);
}
});
Code language: JavaScript (javascript)
Promise 构造函数接受一个回调函数,该函数通常执行异步操作。此函数通常称为执行器。
反过来,执行器接受两个回调函数,分别名为 resolve
和 reject
。
请注意,传递给执行器的回调函数仅按照惯例称为 resolve
和 reject
。
如果异步操作成功完成,执行器将调用 resolve()
函数,将 Promise 的状态从等待中更改为已完成,并带有值。
如果发生错误,执行器将调用 reject()
函数,将 Promise 的状态从等待中更改为拒绝,并带有错误原因。
一旦 Promise 达到已完成或拒绝状态,它将保持该状态,无法再进入其他状态。
换句话说,Promise 不能从 已完成
状态变为 拒绝
状态,反之亦然。此外,它也不能从 已完成
或 拒绝
状态返回到 等待中
状态。
创建新的 Promise
对象后,其状态为等待中。如果 Promise 达到 已完成
或 拒绝
状态,则它已解析。
请注意,在实践中您很少会创建 Promise 对象。相反,您将使用库提供的 Promise。
使用 Promise:then、catch、finally
1) then() 方法
要获取 Promise 在已完成时产生的值,可以调用 Promise 对象的 then()
方法。以下是 then()
方法的语法
promise.then(onFulfilled,onRejected);
Code language: CSS (css)
then()
方法接受两个回调函数:onFulfilled
和 onRejected
。
如果 Promise 已完成,then()
方法将使用值调用 onFulfilled()
;如果 Promise 已拒绝,则使用错误调用 onRejected()
。
请注意,onFulfilled
和 onRejected
参数都是可选的。
以下示例演示如何使用 getUsers()
函数返回的 Promise
对象的 then()
方法
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
});
}
function onFulfilled(users) {
console.log(users);
}
const promise = getUsers();
promise.then(onFulfilled);
Code language: JavaScript (javascript)
输出
[
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' }
]
Code language: JavaScript (javascript)
在本例中
- 首先,定义
onFulfilled()
函数,该函数在 Promise 已完成时被调用。 - 其次,调用
getUsers()
函数以获取 Promise 对象。 - 第三,调用 Promise 对象的
then()
方法并将用户列表输出到控制台。
为了使代码更简洁,您可以使用箭头函数 作为 then()
方法的参数,如下所示
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
}, 1000);
});
}
const promise = getUsers();
promise.then((users) => {
console.log(users);
});
Code language: JavaScript (javascript)
因为 getUsers()
函数返回 Promise 对象,所以您可以使用 then()
方法将函数调用链接起来,如下所示
// getUsers() function
//...
getUsers().then((users) => {
console.log(users);
});
Code language: JavaScript (javascript)
在本例中,getUsers()
函数始终成功。要模拟错误,我们可以使用 success
标志,如下所示
let success = true;
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}
function onFulfilled(users) {
console.log(users);
}
function onRejected(error) {
console.log(error);
}
const promise = getUsers();
promise.then(onFulfilled, onRejected);
Code language: JavaScript (javascript)
工作原理。
首先,定义 success
变量并将其值初始化为 true
。
如果 success
为 true
,则 getUsers()
函数中的 Promise 将使用用户列表完成。否则,它将使用错误消息拒绝。
其次,定义 onFulfilled
和 onRejected
函数。
第三,从 getUsers()
函数获取 Promise 并使用 onFulfilled
和 onRejected
函数调用 then()
方法。
以下是使用箭头函数作为 then()
方法的参数的方法
// getUsers() function
// ...
const promise = getUsers();
promise.then(
(users) => console.log,
(error) => console.log
);
Code language: JavaScript (javascript)
2) catch() 方法
如果只想在 Promise 状态为拒绝时获取错误,可以使用 Promise
对象的 catch()
方法
promise.catch(onRejected);
Code language: CSS (css)
在内部,catch()
方法调用 then(undefined, onRejected)
方法。
以下示例将 success
标志更改为 false
以模拟错误场景
let success = false;
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: '[email protected]' },
{ username: 'jane', email: '[email protected]' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}
const promise = getUsers();
promise.catch((error) => {
console.log(error);
});
Code language: JavaScript (javascript)
3) finally() 方法
有时,您希望无论 Promise 已完成还是拒绝都执行相同的代码段。例如
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
render();
})
.catch((error) => {
console.log(error);
render();
});
Code language: JavaScript (javascript)
如您所见,render()
函数调用在 then()
和 catch()
方法中重复出现。
要消除此重复并执行 render()
(无论 Promise 已完成还是拒绝),可以使用 finally()
方法,如下所示
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
render();
});
Code language: JavaScript (javascript)
一个实用的 JavaScript Promise 示例
以下示例演示如何从服务器加载 JSON 文件并在网页上显示其内容。
假设您有以下 JSON 文件
https://tutorial.javascript.ac.cn/sample/promise/api.json
Code language: JavaScript (javascript)
其内容如下
{
"message": "JavaScript Promise Demo"
}
Code language: JSON / JSON with Comments (json)
以下是包含一个按钮的 HTML 页面。单击按钮时,页面将从 JSON 文件加载数据并显示消息
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JavaScript Promise Demo</title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div id="container">
<div id="message"></div>
<button id="btnGet">Get Message</button>
</div>
<script src="js/promise-demo.js">
</script>
</body>
</html>
Code language: HTML, XML (xml)
以下是 promise-demo.js 文件
function load(url) {
return new Promise(function (resolve, reject) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status == 200) {
resolve(this.response);
} else {
reject(this.status);
}
};
request.open('GET', url, true);
request.send();
});
}
const url = 'https://tutorial.javascript.ac.cn/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');
btn.addEventListener('click', () => {
load(URL)
.then((response) => {
const result = JSON.parse(response);
msg.innerHTML = result.message;
})
.catch((error) => {
msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
});
});
Code language: JavaScript (javascript)
工作原理。
首先,定义 load()
函数,该函数使用 XMLHttpRequest
对象从服务器加载 JSON 文件
function load(url) {
return new Promise(function (resolve, reject) {
const request = new XMLHttpRequest();
request.onreadystatechange = function () {
if (this.readyState === 4 && this.status == 200) {
resolve(this.response);
} else {
reject(this.status);
}
};
request.open('GET', url, true);
request.send();
});
}
Code language: JavaScript (javascript)
在执行器中,如果 HTTP 状态代码为 200,我们将使用响应调用 resolve()
函数。否则,我们将使用 HTTP 状态代码调用 reject()
函数。
其次,注册按钮单击事件侦听器并调用 Promise 对象的 then()
方法。如果加载成功,我们将显示从服务器返回的消息。否则,我们将使用 HTTP 状态代码显示错误消息。
const url = 'https://tutorial.javascript.ac.cn/sample/promise/api.json';
const btn = document.querySelector('#btnGet');
const msg = document.querySelector('#message');
btn.addEventListener('click', () => {
load(URL)
.then((response) => {
const result = JSON.parse(response);
msg.innerHTML = result.message;
})
.catch((error) => {
msg.innerHTML = `Error getting the message, HTTP status: ${error}`;
});
});
Code language: JavaScript (javascript)
摘要
- Promise 是一个封装异步操作结果的对象。
- Promise 从等待中状态开始,最终结束于已完成状态或拒绝状态。
- 使用
then()
方法来安排 Promise 已完成时执行的回调,以及使用catch()
方法来安排 Promise 已拒绝时调用的回调。 - 将您希望在 Promise 已完成或拒绝时执行的代码放在
finally()
方法中。