Создание цепочек промисов

Последнее обновление: 20.11.2023

Одним из примуществом промисов является то, что они позволяют создавать цепочки промисов. Так, ранее мы рассмотрели применение методов then() и catch() для получения и обработки результатов и ошибок асинхронной операции. При выполнении эти методы генерируют новый объект Promise, для которого мы также можем вызвать методы then() и catch(), и, таким образом, построить цепочку промисов. Благодаря этому мы можем обрабатывать подряд несколько асинхронных операций - одна за другой.

promise.then(..).then(..).then(..)

Возвращаемое значение из функции-обработчика в методе then() передается в последующий вызов метода then() в цепочке:

const helloPromise = new Promise(function(resolve){
		resolve("Hello");
})

const worldPromise = helloPromise.then(function(value){
		// возвращаем новое значение
		return value + " World";
});
const metanitPromise = worldPromise.then(function(value){
		// возвращаем новое значение
		return value + " from METANIT.COM";
});
metanitPromise.then(function(finalValue){
		// получаем финальное значение
		console.log(finalValue);	// Hello World from METANIT.COM
});

Здесь для большей ясности весь процесс раздел на раздельные промисы: helloPromise, worldPromise и metanitPromise.

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

Рассмотрим поэтапно.

  1. Сначала создается промис helloPromise:

    const helloPromise = new Promise(function(resolve){
    	resolve("Hello");
    });
    

    В асинхронной операции с помощью вызова resolve("Hello") промис переводится в состояние fulfilled, то есть выполнение операции успешно завершено. А во вне передается значение "Hello".

  2. Далее у промиса helloPromise вызывается метод then():

    const worldPromise = helloPromise.then(function(value){
    		// возвращаем новое значение
    		return value + " World";
    });
    

    В качестве значения параметра value функция обработчика получает строку "Hello" и затем возвращает строку "Hello World". Эта строка затем можно будет получить через метод then() нового промиса, который генерируется вызовом helloPromise.then() и который называется здесь worldPromise.

  3. Затем аналогичным образом у промиса worldPromise вызывается метод then():

    const metanitPromise = worldPromise.then(function(value){
    		// возвращаем новое значение
    		return value + " from METANIT.COM";
    });
    

    В качестве значения параметра value функция обработчика получает строку "Hello World" и затем возвращает строку "Hello World from METANIT.COM". Вызов worldPromise.then() возвращает новый промис metanitPromise.

  4. На последним этапе у промиса metanitPromise вызывается метод then():

    metanitPromise.then(function(finalValue){
    	console.log(finalValue);	// Hello World from METANIT.COM
    });
    

    Здесь через параметр finalValue получаем финальное значение - строку "Hello World from METANIT.COM" и выводим на консоль. После этого цепочка завершена.

Для большей краткости и наглядности мы можем упростить цепочку:

new Promise(resolve => resolve("Hello"))
.then(value => value + " World")
.then(value => value + " from METANIT.COM")
.then(finalValue => console.log(finalValue));

Обработка ошибок

Для обработки ошибок к цепочки в конце добавляется метод catch(), который также возвращет объект Promise. Рассмотрим на простом примере:

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
		.then(value => console.log(value))
		.catch(error => console.log(error));	
}
printNumber("rty");	// Not a number
printNumber("3");	// 3

В данном случае функция generateNumber() возвращает промис, в котором получаем извне некоторое значение, пытаемся конвертировать его в число. В функции printNumber() вызываем эту функцию и у полученного промиса создаем небольшую цепочку из методов then() и catch().

Если конвертация строки в число в промисе прошла успешно, то распарсенное число передачется в функцию resolve():

else resolve(parsed)

В этом случае при получении этого результата срабатывает метод then(), который в данном случае выводит полученное значение на консоль:

.then(value => console.log(value))

Метод catch() при отстутствии ошибок не выполняется.

Однако если передаеваемое значение невозможно конвертировать в число, соответственно в промисе выполняется вызов

if (isNaN(parsed)) reject("Not a number");

В этом случае метод then() игнорируется, и выполнение переходит к вызову

.catch(error => console.log(error));

Обработка ошибок в цепочке промисов

Теперь усложним цепочку. Пусть у нас в цепочке выполняется сразу несколько промисов:

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
		.then(value => {
			if (value===4) throw "Несчастливое число";
			return value * value;
		})
		.then(finalValue => console.log(`Result: ${finalValue}`))
		.catch(error => console.error(error));
}
printNumber("rty");	// Not a number
printNumber("3");	// Result: 9
printNumber("4");	// Несчастливое число
printNumber("5");	// Result: 25

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

Для примера вызываем функцию printNumber(), передавая в нее различные исходные данные. Например, при выполнении вызова

printNumber("rty");	// Not a number

Возвращение Promise из catch

При этом стоит отметить, что, поскольку catch() возвращает объект Promise, то далее также можно продолжить цепочку:

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
		.then(value => value * value)
		.then(value => console.log(`Result: ${value}`))
		.catch(error => console.error(error))
		.then(() => console.log("Work has been done"));
}
printNumber("3");	
// Result: 9
// Work has been done

Причем метод then() после catch() будет вызываться, даже если не произошло ошибок и сам метод catch() не выполнялся.

И мы даже можем из функции-обработчика ошибки в catch() также можем передавать некоторое значение и получать через последующий метод then():

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
		.then(value => value * value)
		.then(value => console.log(`Result: ${value}`))
		.catch(error => {
			console.log(error);
			return 0;
		})
		.then(value => console.log("Status code:", value));
}
printNumber("ert3");	// Not a number
						// Status code: 0

Метод finally

Кроме методов then() и catch() объект Promise для обработка результата также предоставляет метод finally(). Этот метод выполняется в конце цепочки промисов вне зависимости произошла ошибка или выполнение промиса прошло успешно.

В качестве параметра метод принимает функцию, которая выполняет некоторые финальные действия по обработке работы промиса:

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
	.then(value => console.log(value))
	.catch(error => console.log(error))
	.finally(() => console.log("End"));
}

printNumber("3");
printNumber("triuy");

Здесь мы два раза обращаемся к промису, возвращаемому функцией generateNumber. В одном случае строка удачно сконвертируется в число, в другом же - произойдет ошибка. Однако вне зависимости от отсутствия или наличия ошибки в обоих случаях будет выполняться метод finally(), который выведет на консоль строку "End".

Метод finally() возвращает объект Promise, поэтому после него можно продолжить продолжить цепочку:

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
	.then(value => console.log(value))
	.catch(error => console.log(error))
	.finally(() => console.log("Выполнение промиса завершено"))
	.then(() => console.log("Промис все еще работает"));
}
printNumber("3");

Консольный вывод:

3
Выполнение промиса завершено
Промис все еще работает

Стоит отметить что в метод then() также можно передать данные. Но эти данные передаются не из метода finally(), а из предыдущего метода then() или catch():

function generateNumber(str){
    return new Promise((resolve, reject) => {
        const parsed = parseInt(str);
        if (isNaN(parsed)) reject("Not a number");
        else resolve(parsed);
    });
};
function printNumber(str){
	generateNumber(str)
	.then(value => {
		console.log(value);
		return "hello from then";
	})
	.catch(error => {
		console.log(error);
		return "hello from catch";
	})
	.finally(() => {
		console.log("End");
		return "hello from finally";
	})
	.then(message => console.log(message));
}
printNumber("3");
3
End
hello from then
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850