В этом руководстве вы узнаете о промисах 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() - Во-вторых, найдите пользователя с определенным
username, используя методfind()объектаArray. - В-третьих, верните сопоставленного пользователя.
Ниже показан полный код для поиска пользователя с именем '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() для массива users для поиска пользователя с определенным именем пользователя и возвращает совпавшего пользователя.
На практике 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массива и инициализируйте его значение пустым массивом. - Во-вторых, назначьте массив
usersпеременной users внутри обратного вызова функцииsetTimeout(). - В-третьих, верните массив
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() для массива users метод возвращает undefined значение (строка B).
Проблема заключается в том, как получить доступ к users возвращенным getUsers() через одну секунду. Один из классических подходов заключается в использовании обратного вызова.
Использование обратных вызовов для работы с асинхронной операцией
В следующем примере аргумент обратного вызова добавляется к 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() принимает функцию обратного вызова в качестве аргумента и вызывает ее с массивом users внутри функции setTimeout(). Кроме того, findUser() принимает функцию обратного вызова, которая обрабатывает сопоставленного пользователя.
Подход с обратным вызовом работает очень хорошо. Тем не менее, это делает код более трудным для понимания. Кроме того, это усложняет функции с аргументами обратного вызова.
Если количество функций растет, вы можете столкнуться с проблемой ада обратных вызовов. Чтобы решить эту проблему, JavaScript предлагает концепцию промисов.
Понимание промисов
По определению promise — это объект, который инкапсулирует результат асинхронной операции.
Объект обещания имеет состояние, которое может быть одним из следующих:
- В ожидании
- Выполнено со значением
- Отклонено по причине
В начале состояние промиса находится в ожидании, что указывает на то, что асинхронная операция выполняется. В зависимости от результата асинхронной операции состояние меняется либо на выполненное, либо на отклоненное.
Состояние «выполнено» указывает на успешное завершение асинхронной операции:
Состояние «отклонено» указывает на сбой асинхронной операции.
Создание обещания
Чтобы создать объект обещания, вы используете конструктор Promise() :
const promise = new Promise((resolve, reject) => {
// contain an operation
// ...
// return the state
if (success) {
resolve(value);
} else {
reject(error);
}
});Конструктор промисов принимает функцию обратного вызова, которая обычно выполняет асинхронную операцию. Эту функцию часто называют исполнителем.
В свою очередь, исполнитель принимает две callback-функции с именами resolve и reject.
Обратите внимание, что функции обратного вызова, передаваемые исполнителю, resolve и reject только по соглашению.
Если асинхронная операция завершится успешно, исполнитель вызовет функцию resolve(), чтобы изменить состояние промиса с ожидающего на выполненное со значением.
В случае ошибки исполнитель вызовет функцию reject(), чтобы изменить состояние промиса с «ожидание» на «отклонено» с указанием причины ошибки.
Как только обещание достигает состояния выполнено или отклонено, оно остается в этом состоянии и не может перейти в другое состояние.
Другими словами, промис не может перейти из состояния fulfilled в состояние rejected и наоборот. Кроме того, он не может вернуться из состояния fulfilled или rejected в состояние pending.
После создания нового объекта Promise его состояние становится ожидающим. Если обещание достигает fulfilled или rejected состояния, оно разрешается.
Обратите внимание, что на практике вы редко будете создавать промис-объекты. Вместо этого вы будете потреблять обещания, предоставляемые библиотеками.
Применение промисов: then, catch, finally
1) Метод then()
Чтобы получить значение обещания после его выполнения, вы вызываете метод then() объекта обещания. Ниже показан синтаксис метода then() :
promise.then(onFulfilled,onRejected);
Метод then() принимает две функции обратного вызова: onFulfilled и onRejected.
Метод then() вызывает onFulfilled() со значением, если обещание выполнено, или onRejected() с ошибкой, если обещание отклонено.
Обратите внимание, что аргументы onFulfilled и onRejected являются необязательными.
В следующем примере показано, как использовать метод then() объекта Promise, возвращаемого getUsers() :
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' }
]В этом примере:
- Во-первых, определите
onFulfilled(), которая будет вызываться при выполнении обещания. - Во-вторых, вызовите
getUsers(), чтобы получить объект обещания. - В-третьих, вызовите метод
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() возвращает объект промис, вы можете связать вызов функции с методом 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.
Если успех равен true, промис в функции getUsers() выполняется со списком пользователей. В противном случае он отклоняется с сообщением об ошибке.
- Во-вторых, определите функции
onFulfilledиonRejected. - В-третьих, получите обещание из функции
getUsers()и вызовите методthen()сonFulfilledиonRejected.
Ниже показано, как использовать стрелочные функции в качестве аргументов метода then() :
// getUsers() function // ... const promise = getUsers(); promise.then( (users) => console.log, (error) => console.log );
2) Метод catch()
Если вы хотите получить ошибку только тогда, когда состояние обещания отклонено, вы можете использовать метод catch() объекта Promise :
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);
});3) Метод finally()
Иногда вы хотите выполнить один и тот же фрагмент кода независимо от того, выполнено обещание или отклонено. Например:
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
render();
})
.catch((error) => {
console.log(error);
render();
});Как видите, вызов функции render() дублируется как в методах then(), так и в методах catch().
Чтобы удалить этот дубликат и выполнить render() независимо от того, выполнено обещание или отклонено, вы используете метод finally(), например:
const render = () => {
//...
};
getUsers()
.then((users) => {
console.log(users);
})
.catch((error) => {
console.log(error);
})
.finally(() => {
render();
});Практический пример
В следующем примере показано, как загрузить файл JSON с сервера и отобразить его содержимое на веб-странице.
Предположим, у вас есть следующий файл JSON:
https://www.javascripttutorial.net/sample/promise/api.json
Со следующим содержанием:
{
"message": "JavaScript Promise Demo"
}Ниже показана 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>
Ниже показан файл 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://www.javascripttutorial.net/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}`;
});
});Как это работает.
Сначала определите функцию 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();
});
}В исполнителе мы вызываем функцию resolve() с ответом, если код состояния HTTP равен 200. В противном случае мы вызываем функцию reject() с кодом состояния HTTP.
Во-вторых, зарегистрируйте прослушиватель событий нажатия кнопки и вызовите метод then() объекта обещания. Если загрузка прошла успешно, то мы показываем сообщение, возвращенное с сервера. В противном случае показывается сообщение об ошибке с кодом состояния HTTP.
const url = 'https://www.javascripttutorial.net/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}`;
});
});Заключение
- Обещание ( промис) — это объект, который инкапсулирует результат асинхронной операции.
- Промис начинается в состоянии ожидания и заканчивается либо в состоянии выполнено, либо в состоянии отклонения.
- Используйте метод
then(), чтобы запланировать выполнение обратного вызова, когда обещание выполнено, и методcatch(), чтобы запланировать вызов обратного вызова, когда обещание отклонено. - Поместите код, который вы хотите выполнить, в метод
finally()независимо от того, выполнено обещание или отклонено.
