Цепочки промисов в JavaScript

Пусть у нас есть следующий промис:

let promise = new Promise(function(resolve, reject) { setTimeout(function() { resolve('string'); }, 3000); });

По завершению промиса выведем его результат в консоль:

promise.then( function(result) { console.log(result); // выведет 'string' } )

Давайте теперь не будем сразу выводить результат, а как-то изменим его и вернем через return:

promise.then( function(result) { return result + '!'; } );

В этом случае мы можем к результату нашего then применить еще один then, создав тем самым цепочку методов. При этом в результат следующего метода будет попадать то, что вернул через return предыдущий:

promise.then( function(result) { return result + '!'; } ).then( function(result) { console.log(result); // выведет 'string!' } );

Таким образом можно построить цепочку какой-угодно длины:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return result + '2'; } ).then( function(result) { return result + '3'; } ).then( function(result) { console.log(result); // выведет 'string123' } );

Промисы внутри цепочки

Функции цепочки могут также возвращать промисы. В этом случае результат этого промиса попадет в следующий then:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return new Promise(function(resolve) { resolve(result + '2'); // этот результат попадет в следующий then }); } ).then( function(result) { return result + '3'; } ).then( function(result) { console.log(result); // выведет 'string123' } );

Исключительные ситуации

Пусть по каким-то причинам наш промис завершится с ошибкой:

let promise = new Promise(function(resolve, reject) { setTimeout(function() { reject('error'); }, 3000); });

В этом случае выполнение кода сразу перейдет к тому then, в котором есть функция-обработчик ошибки, либо в первому catch, смотря что встретится раньше.

Вот пример первой ситуации:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return result + '2'; }, function(error) { // выполнение сразу перейдет сюда } ).then( function(result) { console.log(result); } );

Вот пример второй ситуации:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return result + '2'; } ).catch( function(error) { // выполнение сразу перейдет сюда } ).then( function(result) { console.log(result); } );

Функция-обработчик имеет два варианта действий: если она справилась с исключительной ситуацией, то может вернуть результат через return и выполнение продолжится дальше по цепочке. Если же она не справилась с ошибкой, то может или ничего не возвращать, или выбросить исключение через throw. В этом случае выполнение перейдет к следующему перехватчику ошибки (в then или catch - что встретится раньше).

Как правило, все ошибки цепочки перехватываются в одном месте: в конце цепочки размещается catch:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return result + '2'; } ).catch( function(error) { // попадем сюда в случае ошибки } );

При этом исключение может возникнуть в самом промисе, либо выброшено через throw в любом звене цепочки:

promise.then( function(result) { return result + '1'; } ).then( function(result) { if (всеХорошо) { return result + '2'; } else { throw new Error('ошибка'); // переходим к ближайшему перехватчику } } ) .then( function(result) { return result + '3'; } ).catch( function(error) { // ближайший перехватчик } );

Учтите, что catch нужен именно для диагностики ошибки: она решаема или нет. Если ошибка решаема, то catch должен передать ее решение следующему за собой then. А если не решаема (или данный catch просто не знает как ее решить), то мы должны или ничего не вернуть или бросить исключение:

promise.then( function(result) { return result + '1'; } ).then( function(result) { return result + '2'; } ).catch( function(error) { if (ошибкаРешаема) { return 'данные'; // отправляем на следующий then } else { // ничего не возвращаем или бросаем исключение } } ).then( function(result) { // тут решаем ошибку } );