在本教程中,您将了解 JavaScript promises 以及如何有效地使用 promises。
JavaScript promises
以下示例定义了一个返回用户对象列表的 getUsers()
函数 :
function getUsers() {
return [
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
];
}
每个用户对象都有两个属性 username
和 email
。
要从 getUsers()
函数返回的用户列表按用户名查找用户,可以使用 findUser()
函数:
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
在 findUser()
函数:
- 首先,通过调用
getUsers()
函数获取用户数组 - 然后,通过
Array
对象的find()
方法找到指定username
的用户。 - 最后,返回匹配的用户。
下面展示如何查找 username 是 'john'
的用户:
function getUsers() {
return [
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
];
}
function findUser(username) {
const users = getUsers();
const user = users.find((user) => user.username === username);
return user;
}
console.log(findUser('john'));
输出:
{ username: 'john', email: 'john@test.com' }
findUser()
函数中的代码是同步并阻塞的。findUser()
函数执行 getUsers()
函数获取用户数组,调用数组 的 find()
方法搜索具有指定用户名的用户,并返回匹配的用户。
实际上,getUsers()
函数可能会访问数据库或调用 API 来获取用户列表。因此,getUsers()
函数会有延迟。
要模拟延迟,可以使用 setTimeout()
函数。例如:
function getUsers() {
let users = [];
// delay 1 second (1000ms)
setTimeout(() => {
users = [
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
];
}, 1000);
return users;
}
代码是如何运行的。
- 首先,定义一个数组
users
并用一个空数组初始化它的值。 - 其次,在
setTimeout()
函数回调内将用户数组分配给users
变量。 - 最后、返回
users
数组。
现在 getUsers()
将无法正常工作并始终返回一个空数组。因此,findUser()
函数不会按预期工作:
function getUsers() {
let users = [];
setTimeout(() => {
users = [
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
];
}, 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'));
输出:
undefined
因为 getUsers()
返回一个空数组,所以 users
数组是空的(A 行)。在数组上调用 find()
方法时,方法返回 undefined
(B 行)
现在的挑战在于如何在一秒钟后访问 getUsers()
函数返回的值 users
。一种经典方法是使用回调。
使用回调处理异步操作
以下示例将回调参数添加到 getUsers()
和 findUser()
函数:
function getUsers(callback) {
setTimeout(() => {
callback([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
}, 1000);
}
function findUser(username, callback) {
getUsers((users) => {
const user = users.find((user) => user.username === username);
callback(user);
});
}
findUser('john', console.log);
输出:
{ username: 'john', email: 'john@test.com' }
在此示例中,getUsers()
函数接受一个回调函数作为参数,并在 setTimeout()
函数内指定 users
数组作为回调函数参数并调用。此外,findUser()
函数接受用于处理匹配用户的回调函数。
回调方法非常有效。但是,它使代码更难以理解。此外,它还增加带有回调参数的函数的复杂性。
如果函数数量增加,您可能会遇到回调黑洞问题。为了解决这个问题,JavaScript 提出 promises 的概念。
理解 JavaScript Promise
根据定义,promise 是一个封装异步操作结果的对象。
promise 对象的状态可以是以下之一:
- Pending (等待)
- Fulfilled with a value (值已经填充)
- Rejected for a reason (拒绝与指定的原因)
一开始,promise 的状态是 pending,表示异步操作正在进行中。根据异步操作的结果,状态更改为已完成或已拒绝。
fulfilled 状态表示异步操作已成功完成:
拒绝状态表示异步操作失败。
创建 Promise
要创建一个 promise 对象,您可以使用构造函数 Promise()
:
const promise = new Promise((resolve, reject) => {
// 做一些阻塞的操作
// ...
// return the state
if (success) {
resolve(value);
} else {
reject(error);
}
});
promise 构造函数接受执行异步操作的回调函数。这个函数通常被称为执行器。反过来,执行器接受称为 resolve
和 reject
的两个回调函数。
请注意,传递给执行执行器的回调函数resolve
和reject
只是约定俗成的命名。
如果异步操作成功完成,执行器将调用 resolve()
函数以将 promise 的状态从待定状态更改为已完成。
如果出现错误,执行器将调用函数 reject()
将 promise 的状态从挂起更改为拒绝,并给出错误原因。
一旦 promise 达到 fulfilled 或 rejected 状态,它就会停留在状态并且不能进入另一个状态。
换句话说,一个 promise 不能从一个 fulfilled
状态到 rejected
状态,反之亦然。此外,它不能从 fulfilled
或 rejected
状态返回到 pending
状态。
一旦创建了一个 Promise
对象,它的状态就是挂起的。如果一个 promise 到达fulfilled
或 rejected
状态,它就被 Resolved(解决)。
请注意,您在实践中很少会创建 promise 对象。相反,您将使用库提供的promise。
Promise:then, catch, finally
then() 方法
要在 Promise 完成时获取 Promise 的值,您可以调用 Promise 对象的 then()
方法。下面展示 then()
方法的语法:
promise.then(onFulfilled,onRejected);
then()
方法接受两个回调函数:onFulfilled
和 onRejected
。
如果 Promise 完成,then()
方法会调用 onFulfilled()
与一个值,如果 Promise 被拒绝,onRejected()
方法会被调用与一个错误原因。
请注意,then()
方法的参数onFulfilled
和onRejected
参数都是可选的。
以下示例展示getUsers()
函数如何使用 Promise
对象的 then()
方法返回用户数组:
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
}, 1000);
});
}
function onFulfilled(users) {
console.log(users);
}
const promise = getUsers();
promise.then(onFulfilled);
输出:
[
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' }
]
在这个例子中:
- 首先,定义Promise 完成时要调用的
onFulfilled()
函数。 - 其次,调用
getUsers()
函数返回一个 promise 对象。 - 第三,调用 promise 对象的
then()
方法,将用户列表输出到控制台。
为了使代码更简洁,您可以使用箭头函数作为 then()
方法的参数,如下所示:
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
}, 1000);
});
}
const promise = getUsers();
promise.then((users) => {
console.log(users);
});
因为 getUsers()
函数返回一个 promise 对象,所以您可以使用如下方法链式调用 then()
方法:
// getUsers() function
//...
getUsers().then((users) => {
console.log(users);
});
在此示例中,getUsers()
函数总是成功。为了模拟错误,我们可以使用 success 变量模拟错误:
let success = true;
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
} 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);
代码如何运行的。
首先,定义 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
);
catch() 方法
如果你想在 promise 的状态被拒绝时得到错误,你可以使用 Promise
对象 catch()
的方法:
promise.catch(onRejected);
在内部,catch()
方法调用 then(undefined, onRejected)
方法。
以下示例 success
变量更改为 false
模拟错误场景:
let success = false;
function getUsers() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve([
{ username: 'john', email: 'john@test.com' },
{ username: 'jane', email: 'jane@test.com' },
]);
} else {
reject('Failed to the user list');
}
}, 1000);
});
}
const promise = getUsers();
promise.catch((error) => {
console.log(error);
});
finally() 方法
有时,无论 promise 是完成还是拒绝,您都希望执行同一段代码。例如:
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
render();
})
.catch((error) => {
console.log(error);
render();
});
如您所见,render()
函数调用是重复的,在 then()
和 catch()
方法。要删除此重复,无论 Promise 是完成还是拒绝都执行 render()
函数,您可以使用 Promise 对象的 finally()
方法。
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
render();
});
结论
- Promise 是封装异步操作结果的对象。
- promise 以 pending 状态开始,以 fulfilled 状态或 rejected 状态结束。
- 使用
then()
方法来安排在 Promise 完成时执行的回调,并使用catch()
方法来安排在 Promise 被拒绝时调用的回调。 - 无论 Promise 是完成还是拒绝都要执行指定的代码,可以将代码放在
finally()
方法。