Последнее обновление: 30.08.2021
Одним из преимуществ промисов является более простая обработка ошибок. Для получения и обработки ошибки мы можем использовать
функцию catch() объекта Promise
, которая в качестве параметра принимает функцию обработчика ошибки:
const myPromise = new Promise(function(resolve, reject){ console.log("Выполнение асинхронной операции"); reject("Переданы некорректные данные"); }); myPromise.catch( function(error){ console.log(error); });
Функция catch() в качестве параметра принимает обработчик ошибки. Параметром этой функции-обработчика является то значение,
которое передается в reject()
.
Консольный вывод:
Выполнение асинхронной операции Переданы некорректные данные
Генерация ошибки
Выше для извещения о возникшей ошибке вызывалась функция reject()
. Но стоит отметить, что ошибка может возникнуть и без вызова функции
reject()
. И если в выполняемой в промисе операции генерируется ошибка в силу тех или иных причин, то вся операция также завершается ошибкой.
Например, в следующем коде вызывается нигде не определенная функция getSomeWork()
:
const myPromise = new Promise(function(resolve){ console.log("Выполнение асинхронной операции"); getSomeWork(); // вызов не существующей функции resolve("Hello world!"); }); myPromise.catch( function(error){ console.log(error); });
Поскольку функция getSomeWork()
нигде не объявлена, то выполнение асинхронной задачи завершится ошибкой и не дойдет до вызова resolve("Hello world!")
.
Поэтому сработает функция обработки ошибок из catch()
, которая через параметр error получит информацию о возникшей ошибке, и
в консоли браузера мы увидим сообщение об ошибке:
Выполнение асинхронной операции ReferenceError: getSomeWork is not defined at index.html:39 at new Promise (<anonymous>) at index.html:37
throw
Также ошибка может быть результатом вызова оператора throw, который генерирует ошибку:
cconst myPromise = new Promise(function(resolve, reject){ console.log("Выполнение асинхронной операции"); const parsed = parseInt("Hello"); if (isNaN(parsed)) { throw "Not a number"; // Генерируем ошибку } resolve(parsed); }); myPromise.catch( function(error){ console.log(error); });
Здесь парсится в число случайная строка. И если результат парсинга не представляет число, то с помощью оператора throw
генерируем ошибку.
Это придет к завершению всей функции с ошибкой. И в итоге результат будет обработан функцией catch
:
Выполнение асинхронной операции Not a number
В этом случае функция обработчика получает сообщение об оошибке, который указывается после оператора throw
.
Данная ситуация может показаться искуственной, так как нам нет смысла генерировать в коде выше ошибку с помощью throw, поскольку в этом случае мы также
можем передать сообщение об ошибке в функцию reject:
if (isNaN(parsed)) { reject("Not a number"); }
Однако данный оператор может применяться во внешней функции, которую мы вызываем в коде:
function getNumber(str){ const parsed = parseInt(str); if (isNaN(parsed)) throw "Not a number"; // Генерируем ошибку else return parsed; } const myPromise = new Promise(function(resolve){ console.log("Выполнение асинхронной операции"); const result = getNumber("hello"); resolve(result); }); myPromise.catch( function(error){ console.log(error); });
Здесь парсинг строки в число вынесен во внешнюю функцию — getNumber
, однако при вызове этой функции в промисе, также из оператора throw возникнет ошибка.
И соответственно будет выполняться функция catch()
, где роизойдет обработка ошибки.
try..catch
Как и в общем случае, операции, которые могут генерировать ошибку, можно помещать в конструкцию try..catch
, а при возникновении исключения в блоке catch вызывать функцию reject()
:
const myPromise = new Promise(function(resolve, reject){ try{ console.log("Выполнение асинхронной операции"); getSomeWork(); // вызов не существующей функции resolve("Hello world!"); } catch(err){ reject(`Произошла ошибка: ${err.message}`); } }); myPromise.catch( function(error){ console.log(error); });
Консольный вывод:
Выполнение асинхронной операции Произошла ошибка: getSomeWork is not defined
Обработка ошибки с помощью функции then
Кроме функции catch
для получения информации об ошибке и ее обработки также можно использовать функцию
then() — ее второй параметр представляет обработчик ошибки, который в качестве параметра получает переданное из функции
reject
значение:
promise .then(function(value){ // получение значения }, function(error){ // обработка ошибки });
Второй параметр функции then()
представляет функцию обработчика ошибок. С помощью параметра error
в функции-обработчика мы можем получить переданное в reject()
значение, либо информацию о возникшей ошибке.
Рассмотрим следуюший пример:
function generateNumber(str){ return new Promise(function(resolve, reject){ const parsed = parseInt(str); if (isNaN(parsed)) reject("значение не является числом") else resolve(parsed); }) .then(function(value){ console.log("Результат операции:", value);}, function(error){ console.log("Возникла ошибка:", error);}); } generateNumber("23"); generateNumber("hello");
В данном случае для того, чтобы в промис можно было передать разные данные, он определен как возващаемый результат функции generateNumber()
. То есть в данном случае консольный вывод будет следующим:
Результат операции: 23 Возникла ошибка: значение не является числом
Время на прочтение
8 мин
Количество просмотров 90K
Доброго времени суток, Хабр! Представляю вашему вниманию перевод статьи «Understanding Promises in JavaScript» автора Sukhjinder Arora.
От автора перевода: Так же, как и сам автор, я надеюсь, что статья оказалась для вас полезной. Пожалуйста, если она и вправду помогла вам узнать для себя что-то новое, то не поленитесь зайти на оригинал статьи и поблагодарить автора! Буду рад вашему фидбеку!
Ссылка на перевод статьи по асинхронному JavaScript от этого же автора.
JavaScript — это однопоточный язык программирования, это означает, что за раз может быть выполнено что-то одно. До ES6 мы использовали обратные вызовы, чтобы управлять асинхронными задачами, такими как сетевой запрос.
Используя промисы, мы можем избегать “ад обратных вызовов” и сделать наш код чище, более читабельным и более простым для понимания.
Предположим, что мы хотим асинхронно получить некоторые данные с сервера, используя обратные вызовы мы сделали бы что-то вроде этого:
getData(function(x){
console.log(x);
getMoreData(x, function(y){
console.log(y);
getSomeMoreData(y, function(z){
console.log(z);
});
});
});
Здесь я запрашиваю некоторые данные с сервера при помощи функции getData(), которая получает данные внутри функции обратного вызова. Внутри функции обратного вызова я запрашиваю дополнительные данные при помощи вызова функции getMoreData(), передавая предыдущие данные как аргумент и так далее.
Это то, что мы называем “адом обратных вызовов”, где каждый обратный вызов вложен внутрь другого, и каждый внутренний обратный вызов зависит от его родителя.
Мы можем переписать приведенный выше фрагмент используя промисы:
getData()
.then((x) => {
console.log(x);
return getMoreData(x);
})
.then((y) => {
console.log(y);
return getSomeMoreData(y);
})
.then((z) => {
console.log(z);
});
Вы можете видеть, что стало более читабельно, чем в случае первого примера с обратными вызовами.
Что такое Промисы?
Промис(Обещание) — это объект который содержит будущее значение асинхронной операции. Например, если вы запрашиваете некоторые данные с сервера, промис обещает нам получить эти данные, которые мы сможем использовать в будущем.
Прежде чем погрузиться во все эти технические штуки, давайте разберемся с терминологией промисов.
Состояния промисов
Промис в JavaScript, как и обещание в реальной жизни, имеет 3 состояния. Это может быть 1) нерешенный(в ожидании), 2) решенный/resolved (выполненный) или 3) отклоненный/rejected.
Нерешенный или Ожидающий — Промис ожидает, если результат не готов. То есть, ожидает завершение чего-либо(например, завершения асинхронной операции).
Решенный или Выполненный — Промис решен, если результат доступен. То есть, что-то завершило свое выполнение(например, асинхронная операция) и все прошло хорошо.
Отклоненный — Промиc отклонен, если произошла ошибка в процессе выполнения.
Теперь мы знаем, что такое Промис и его терминологию, давайте вернемся назад к практической части промисов.
Создаем Промис
В большинстве случаев вы будете просто использовать промисы, а не создавать их, но все же важно знать как они создаются.
Синтаксис:
const promise = new Promise((resolve, reject) => {
...
});
Мы создали новый промис, используя конструктор Промисов, он принимает один аргумент, обратный вызов, также известный как исполнительная функция, которая принимает 2 обратных вызова, resolve и reject.
Исполнительная функция выполняется сразу же после создания промиса. Промис становится выполненным при помощи вызова resolve(), а отклоненным при помощи reject(). Например:
const promise = new Promise((resolve, reject) => {
if(allWentWell) {
resolve('Все прошло отлично!');
} else {
reject('Что-то пошло не так');
}
});
resolve() и reject() принимают один аргумент, который может быть строкой, числом, логическим выражением, массивом или объектом.
Давайте взглянем на другой пример, чтобы полностью понять как создаются промисы.
const promise = new Promise((resolve, reject) => {
const randomNumber = Math.random();
setTimeout(() => {
if(randomNumber < .6) {
resolve('Все прошло отлично!');
} else {
reject('Что-то пошло не так');
}
}, 2000);
});
Здесь я создал новый промис используя конструктор Промисов. Промис выполняется или отклоняется через 2 секунды после его создания. Промис выполняется, если randomNumber меньше, чем .6 и отклоняется в остальных случаях.
Когда промис был создан, он будет в состоянии ожидания и его значение будет undefined.
После 2 секунд таймер заканчивается, промис случайным образом либо выполняется, либо отклоняется, и его значением будет то, которое передано в функцию resolve или reject. Ниже пример двух случаев:
Успешное выполнение:
Отклонение промиса:
Примечание: Промис может быть выполнен или отклонен только один раз. Дальнейшие вызовы resolve() или reject() никак не повлияют на состояние промиса. Пример:
const promise = new Promise((resolve, reject) => {
resolve('Promise resolved'); // Промис выполнен
reject('Promise rejected'); // Промис уже не может быть отклонен
});
Так как resolve() была вызвана первой, то промис теперь получается статус “выполненный”. Последующий вызов reject() никак не повлияет на состояние промиса.
Использование Промиса
Теперь мы знаем как создавать промисы, давайте теперь разберемся как применять уже созданный промис. Мы используем промисы при помощи методов then() и catch().
Например, запрос данных из API при помощи fetch, которая возвращает промис.
.then() синтаксис: promise.then(successCallback, failureCallback)
successCallback вызывается, если промис был успешно выполнен. Принимает один аргумент, который является значением переданным в resolve().
failureCallback вызывается, если промис был отклонен. Принимает один аргумент, который является значением преданным в reject().
Пример:
const promise = new Promise((resolve, reject) => {
const randomNumber = Math.random();
if(randomNumber < .7) {
resolve('Все прошло отлично!');
} else {
reject(new Error('Что-то пошло не так'));
}
});
promise.then((data) => {
console.log(data); // вывести 'Все прошло отлично!'
},
(error) => {
console.log(error); // вывести ошибку
}
);
Если промис был выполнен, то вызывается successCallback со значением, переданным в resolve(). И если промис был отклонен, то вызывается failureCallback со значением, переданным в reject().
.catch() синтаксис: promise.catch(failureCallback)
Мы используем catch() для обработки ошибок. Это более читабельно, нежели обработка ошибок внутри failureCallback внутри обратного вызова метода then().
const promise = new Promise((resolve, reject) => {
reject(new Error('Что-то пошло не так'));
});
promise
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error); // вывести ошибку
});
Цепочка промисов
Методы then() и catch() также могут возвращать новый промис, который может быть обработан цепочкой других then() в конце предыдущего метода then().
Мы используем цепочку промисов, когда хотим выполнить последовательность промисов.
Например:
const promise1 = new Promise((resolve, reject) => {
resolve('Promise1 выполнен');
});
const promise2 = new Promise((resolve, reject) => {
resolve('Promise2 выполнен');
});
const promise3 = new Promise((resolve, reject) => {
reject('Promise3 отклонен');
});
promise1
.then((data) => {
console.log(data); // Promise1 выполнен
return promise2;
})
.then((data) => {
console.log(data); // Promise2 выполнен
return promise3;
})
.then((data) => {
console.log(data);
})
.catch((error) => {
console.log(error); // Promise3 отклонен
});
И так, что тут происходит?
Когда promise1 выполнен, вызывается метод then(), который возвращает promise2.
Далее, когда выполнен promise2, снова вызывается then() и возвращает promise3.
Так как promise3 отклонен, вместо следующего then(), вызывается catch(), который и обрабатывает отклонение promise3.
Примечание: Как правило достаточно одного метода catch() для обработки отклонения любого из промисов в цепочке, если этот метод находится в конце неё.
Распространенная ошибка
Достаточно много новичков делают ошибку, вкладывая одни промисы внутрь других. Например:
const promise1 = new Promise((resolve, reject) => {
resolve('Promise1 выполнен');
});
const promise2 = new Promise((resolve, reject) => {
resolve('Promise2 выполнен');
});
const promise3 = new Promise((resolve, reject) => {
reject('Promise3 отклонен');
});
promise1.then((data) => {
console.log(data); // Promise1 выполнен
promise2.then((data) => {
console.log(data); // Promise2 выполнен
promise3.then((data) => {
console.log(data);
}).catch((error) => {
console.log(error); // Promise3 отклонен
});
}).catch((error) => {
console.log(error);
})
}).catch((error) => {
console.log(error);
});
Несмотря на то, что это будет работать нормально, это считается плохим стилем и делает код менее читабельным. Если у вас есть последовательность промисов для выполнения, будет лучше ставить их один за другим, нежели вкладывать один внутрь другого.
Promise.all( )
Этот метод берет массив промисов и возвращает новый промис, который будет выполненным, когда все промисы внутри массива выполнены или отклонен, как только встречается промис, который отклоняется. Например:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1 выполнен');
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise2 выполнен');
}, 1500);
});
Promise.all([promise1, promise2])
.then((data) => console.log(data[0], data[1]))
.catch((error) => console.log(error));
Здесь аргументом внутри then() выступает массив, который содержит значения промисов в том же порядке, в котором они передавались в Promise.all().(Только в том случае, если все промисы выполняются)
Промис отклоняется с причиной отклонения первого промиса в переданном массиве. Например:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1 выполнен');
}, 2000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise2 отклонен');
}, 1500);
});
Promise.all([promise1, promise2])
.then((data) => console.log(data[0], data[1]))
.catch((error) => console.log(error)); // Promise2 отклонен
Здесь у нас есть два промиса, где один выполняется через 2 секунды, а другой отклоняется через 1.5 секунды. Как только второй промис отклоняется, возвращенный от Promise.all() промис отклоняется не дожидаясь выполнения первого.
Этот метод может быть полезен, когда у вас есть более одного промиса и вы хотите знать, когда все промисы выполнены. Например, если вы запрашиваете данные из стороннего API и вы хотите что-то сделать с этими данными только тогда, когда все запросы проходят успешно.
По итогу мы имеем Promise.all(), который ждет успешное выполнение всех промисов, либо завершает свое выполнение при обнаружении первой неудачи в массиве промисов.
Promise.race( )
Этот метод принимает массив промисов и возвращает один новый промис, который будет выполненным, как только встретится выполненный промис в массиве или же отклоняется, если отклоненный промис встречается раньше. Например:
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise1 выполнен');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('Promise2 отклонен');
}, 1500);
});
Promise.race([promise1, promise2])
.then((data) => console.log(data)) // Promise1 выполнен
.catch((error) => console.log(error));
Тут мы имеем два промиса, где один выполняется через 1 секунду, а другой отклоняется через 1.5 секунды. Как только первый промис выполнен, возвращенный из Promise.race() промис будет иметь статус выполненного не дожидаясь статуса второго промиса.
Здесь data, которая передается в then() является значением первого, выполненного, промиса.
По итогу, Promise.race() дожидается первого промиса и берет его статус как статус возвращаемого промиса.
Комментарий автора перевода: Отсюда собственно и название. Race — гонка
Заключение
Мы узнали, что такое промисы и с чем их едят в JavaScript. Промисы состоят из двух частей 1) Создание промиса и 2) Использование промиса. Большую часть времени вы будете пользоваться промисами, нежели создавать их, но важно знать как они создаются.
Вот и все, надеюсь эта статья оказалась для вас полезной!
Rule of Thumb
Whenever you have a doubt about how to do something with promises — think about the synchronous version.
try{
var result = myFn(param);
// business logic with result
} catch(e) {
//error handling logic
}
This, at least to me looks a lot cleaner than a callback with a first parameter that is sometimes null
.
The promises way is almost always very similar to the synchronous version of the problem:
myFn(param).then(function(result){
// business logic with result
}).catch(function(e){
//error handling logic
});
Where myFn would look something like when working with callbacks:
var myFn = function(param){
return new Promise(function(resolve, reject){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
reject(new Error("error with calculation"), null);
}
someAsyncOp(calcN,function(err, finalResult){
if(err) reject(err);
resolve(finalResult);
})
});
};
Working with callbacks/nodebacks
This is only something you should have to do when working with callbacks, when working with promises it is a lot simpler, and you can do:
var myFn = function(param){
var calc = doSomeCalculation(param);
...
return someAsyncOp(calcN); // returning a promise.
}
Moreover, when working inside promise chains, you get throw safety:
myFn(param).then(function(calcN){
// here, you throw to raise an error and return to resolve
// new Promise should be used only when starting a chain.
}).catch(function(err){
// handle error
}).then(function(){
// ready to go again, we're out of the catch
});
Note, some libraries like Bluebird , RSVP and Q offer syntactic sugar and automatic promisification of methods so you rarely have to use new Promise
yourself.
Also, consider reading this and that to learn more about promise error handling.
Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером
Обработка ошибок в промисах
—
JS: Асинхронное программирование
Ошибки внутри промисов обрабатываются крайне просто. Для перехвата достаточно вызвать метод catch
и передать туда колбек, принимающий на вход саму ошибку:
import fsp from 'fs/promises';
const promise = fsp.readFile('unknownfile');
promise.catch((e) => console.log('error!!!', e));
// => error!!! { [Error: ENOENT: no such file or directory, open 'unknownfile']
// errno: -2, code: 'ENOENT', syscall: 'open', path: 'unknownfile' }
catch
, в свою очередь, возвращает promise, что позволяет коду восстанавливать работу после ошибок и продолжать цепочку. Вполне нормально писать код в стиле цепочки, в которой чередуются then
и catch
:
import fsp from 'fs/promises';
const promise = fsp.readFile('unknownfile')
.catch(console.log)
.then(() => fsp.readFile('anotherUnknownFile'))
.catch(console.log);
В большинстве ситуаций не имеет значения, на какой из операций возникла ошибка. Любое падение должно прерывать текущее выполнение и уходить в блок обработки ошибки. Именно так работает код с try/catch, и такое же поведение эмулируется промисами. Дело в том, что если возникла ошибка, то она передается по цепочке первому встреченному catch
, а все встречающиеся на пути then
игнорируются. Поэтому код выше можно упростить так:
import fsp from 'fs/promises';
const promise = fsp.readFile('unknownfile')
.then(() => fsp.readFile('anotherUnknownFile'))
.catch(console.log);
Семантически эти версии кода не эквивалентны. В первом случае вторая операция чтения начнет выполняться обязательно, независимо от того, как закончилась предыдущая. В последнем — если упадет первое чтение файла, то второе не будет выполнено.
Иногда ошибку нужно генерировать самостоятельно. Самый простой способ сделать это — бросить исключение. К этому тоже надо привыкнуть. try/catch использовать нельзя (потому что бесполезно), а вот бросать исключения можно. Промис сам их преобразует, как надо, и отправит по цепочке в поиске вызова catch
:
import fsp from 'fs/promises';
const promise = fsp.readFile('unknownfile')
.then((data) => {
// делаем что-нибудь
throw new Error('boom!');
})
.then(() => {
// Этот then не будет вызван из—за исключения на предыдущем шаге
})
.catch(console.log);
Другой способ — вернуть результат вызова функции Promise.reject
, внутрь которой передается сама ошибка:
import fsp from 'fs/promises';
const promise = fsp.readFile('unknownfile')
.then((data) => {
// делаем что-нибудь
return Promise.reject(new Error('boom!'));
})
.catch(console.log);
Помимо чисто технических моментов в обработке ошибок есть и архитектурно-организационные. Если вам приходится реализовывать асинхронные функции, которыми будут пользоваться другие люди, то никогда не подавляйте ошибки:
import fsp from 'fs/promises';
// Подавление ошибки при помощи catch()
const readFileEasily = (filepath) => fsp.readFile(filepath).catch(console.log);
Перехватив ошибку, вы не оставляете шансов узнать о ней вызывающему коду. Тот, кто использует эту функцию, не сможет отреагировать на ошибочную ситуацию. Если обработка ошибки все же нужна — обрабатывайте, но не забывайте генерировать ее снова:
import fsp from 'fs/promises';
// Перехватывая ошибку при помощи catch(), мы не даем ей попасть в вызывающий код
// Это нежелательное поведение — в конце ошибку нужно выбросить снова
const readFileEasily = (filepath) => fsp.readFile(filepath)
.catch((e) => {
console.log(e); // В библиотеках так делать нельзя, только в своем коде
throw e;
});
// Теперь вызывающий код может обработать ошибку:
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
-
130 курсов, 2000+ часов теории -
1000 практических заданий в браузере -
360 000 студентов
Наши выпускники работают в компаниях:
Разбираемся, как работать с Promise.resolve, где лучше обрабатывать исключения Promise.reject, и какие еще есть хитрости в работе с асинхронным JS.
1. Вернуть Promise можно внутри .then
Возвращенный promise сразу будет готов к использованию в следующем .then:
.then(r => { return serverStatusPromise(r); // { statusCode: 200 } }) .then(resp => { console.log(resp.statusCode); // 200; помните об автоматическом разворачивании промисов })
2. Новый Promise создается при каждом .then
Если вы знакомы с цепочкой вызовов в JavaScript, то вы и так все знаете. Тем же, кто не в курсе, как это работает, некоторые вещи могут показаться не очевидными.
Каждый раз, когда вы используете .then или .catch к промисам, вы создаете новый. Новая сущность представляет собой композицию промиса и вызова .then или .catch, который был привязан.
На примере:
var statusProm = fetchServerStatus(); var promA = statusProm.then(r => (r.statusCode === 200 ? "good" : "bad")); var promB = promA.then(r => (r === "good" ? "ALL OK" : "NOTOK")); var promC = statusProm.then(r => fetchThisAnotherThing());
Взаимоотношения вызовов в коде выше можно описать такой схемой:
Важно также, что promA, promB и promC – разные промисы, но родственные.
Если один промис используют несколько частей приложения, каждая часть будет оповещена, когда он получит состояние resolve/reject. Это также означает, что никто не сможет изменить ваш промис, так что его можно передавать без опаски.
function yourFunc() { const yourAwesomeProm = makeMeProm(); yourEvilUncle(yourAwesomeProm); // будьте уверены, промис будет работать несмотря на то, // как его употребит злой дядя return yourAwesomeProm.then(r => importantProcessing(r)); } function yourEvilUncle(prom) { return prom.then(r => Promise.reject("destroy!!")); }
В примере выше видно, что Promise по определению трудно изменяем.
4. Конструктор промисов не решает проблему
Многие разработчики повсеместно используют конструктор с мыслью, что все делают правильно. В действительности, API конструктора очень похоже на старое доброе Callback API.
Чтобы на самом деле отойти от callback’ов, необходимо уменьшить количество promise-конструкторов, которые вы используете.
Вот реальный случай использования конструктора:
return new Promise((res, rej) => { fs.readFile("/etc/passwd", function(err, data) { if (err) return rej(err); return res(data); }); });
Promise constructor понадобится только в том случае, когда вам нужно конвертировать callback в промис.
Однажды овладев этим способом создания промисов, велик будет соблазн использовать его всюду.
Вот пример ненужного использования конструктора:
return new Promise((res, rej) => { var fetchPromise = fetchSomeData(.....); fetchPromise .then(data => { res(data); }) .catch(err => rej(err)) })
А правильно так:
return fetchSomeData(...);
Заворачивание Promise в конструктор излишне и убивает всю пользу.
Если вы работаете с Node.js, присмотритесь к util-promisify. Эта маленькая штука поможет преобразовывать колбеки Node.js в промисы.
const {promisify} = require('util'); const fs = require('fs'); const readFileAsync = promisify(fs.readFile); readFileAsync('myfile.txt', 'utf-8') .then(r => console.log(r)) .catch(e => console.error(e));
5. Использование Promise.resolve
Promise.resolve помогает сокращать некоторые сложные конструкции, как в примере:
var similarProm = new Promise(res => res(5)); // ^^ то же самое var prom = Promise.resolve(5);
У .resolve множество вариантов применения, один из них помогает конвертировать обычный JS-объект в promise:
// конвертируем синхронную функцию в асинхронную function foo() { return Promise.resolve(5); }
Этот метод можно использовать как оболочку для значений, когда точно не известно промис это или простое значение.
function goodProm(maybePromise) { return Promise.resolve(maybePromise); } goodProm(5).then(console.log); // 5 var sixPromise = fetchMeNumber(6); // промис, который разрешается в 5 goodProm(sixPromise).then(console.log); // 6 goodProm(Promise.resolve(Promise.resolve(5))).then(console.log); // 5, обратите внимание, что все слои промиса будут автоматически развернуты
6. Использование Promise.reject
Promise.reject может послужить заменой такому коду:
var rejProm = new Promise((res, reject) => reject(5)); rejProm.catch(e => console.log(e)) // 5
Функционал Promise.reject полностью оправдывает свое имя – он нужен чтобы отклонить промис.
function foo(myVal) { if (!mVal) { return Promise.reject(new Error('myVal is required')) } return new Promise((res, rej) => { // конвертация колбека }) }
В примере ниже reject используется внутри .then:
.then(val => { if (val != 5) { return Promise.reject('Not Good'); } }) .catch(e => console.log(e)) // Not Good
7. Использование Promise.all
Алгоритм работы Promise.all можно описать примерно так:
Принимает множество Promise затем ждет, пока все они завершатся затем возвращает новый промис, который разрешается в массив улавливает ошибку, если один из них отклоняется
Следующий пример показывает, когда все промисы разрешаются:
var prom1 = Promise.resolve(5); var prom2 = fetchServerStatus(); // вернет промис {statusCode: 200} Proimise.all([prom1, prom2]) .then([val1, val2] => { // на выходе будет массив console.log(val1); // 5 console.log(val2.statusCode); // 200 })
Этот пример показывает, когда один из них терпит неудачу:
var prom1 = Promise.reject(5); var prom2 = fetchServerStatus(); // вернет промис {statusCode: 200} Proimise.all([prom1, prom2]) .then([val1, val2] => { console.log(val1); console.log(val2.statusCode); }) .catch(e => console.log(e)) // 5, перейдет сразу к .catch
8. Обрабатывайте отказы уровнем выше
Оставляйте решение проблем с rejection родительским функциям. В идеале, обработка отказов должна находиться в корне приложения, и все Promise.reject должны обрабатываться там.
Не стесняйтесь писать код вроде этого:
return fetchSomeData(...);
В этом случае, чтобы обработать rejection функции, нужно решить, разрешать работу как есть или продолжить обработку отказа. Есть небольшая уловка при работе с catch. Если вернуть Promise.reject в catch, то он будет отклонен.
.then(() => 5.length) // <-- что-то не так .catch(e => { return 5; // <-- решаем проблему }) .then(r => { console.log(r); // 5 }) .catch(e => { console.error(e); // эта функция не будет вызвана })
Чтобы отклонить reject достаточно ничего не делать. Пусть это будет проблемой других функций. Чаще всего родительские функции имеют больше возможностей для обработки отказов, чем функция, в которой произошел reject.
Важно помнить, что если вы пишете .catch, значит, собираетесь обрабатывать ошибки. Если нужно перехватить отказ:
.then(() => 5.length) // <-- что-то не так .catch(e => { errorLogger(e); // неявное действие return Promise.reject(e); // отклоняется }) .then(r => { console.log(r); // этот .then (или любой последующий) никогда не будет вызван, // так как был отклонен выше }) .catch(e => { console.error(e); //<-- будет разрешатся текущим .catch })
.then принимает второй параметр, который можно использовать для обработки ошибок. Это может напомнить then(x).catch(x), но эти обработчики по разному обрабатывают ошибки.
.then(function() { return Promise.reject(new Error('something wrong happened')); }).catch(function(e) { console.error(e); // что-то не так }); .then(function() { return Promise.reject(new Error('something wrong happened')); }, function(e) { // callback обрабатывает ошибку из цепочки текущего .then console.error(e); // ошибок не будет });
9. Избегайте нагромождений .then
Совет довольно прост: избегайте использования .then внутри .then или .catch.
Неверно:
request(opts) .catch(err => { if (err.statusCode === 400) { return request(opts) .then(r => r.text()) .catch(err2 => console.error(err2)) } })
Верно:
request(opts) .catch(err => { if (err.statusCode === 400) { return request(opts); } return Promise.reject(err); }) .then(r => r.text()) .catch(err => console.erro(err));
Порой бывает так, что необходимо множество переменных в пределах .then, и нет других вариантов, кроме цепочки вызовов.
.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return promA .then(valA => { return promB.then(valB => hungryFunc(valA, valB)); }) })
Можно использовать ES6 подход к деструктуризации и Promise.all:
.then(myVal => { const promA = foo(myVal); const promB = anotherPromMake(myVal); return Promise.all([prom, anotherProm]) }) .then(([valA, valB]) => { // ES6 console.log(valA, valB) // все разрешенные значения return hungryFunc(valA, valB) })
Больше полезного по JS:
- Разбираем JavaScript код: 7 проблем, ухудшающих читабельность
- TOП-12 JavaScript-концепций: от ссылок до асинхронных операций
- 15 советов по программированию на языке JavaScript
- Топ 15 вопросов о React.JS на собеседовании