Что такое Промисы?

Здравствуйте! Прошу подсказать по теории… Мне не совсем понятно, в чём отличие между .then и catch. До прочтения данной темы здесь, мне казалось, что у then и catch разные задачи. then призван выполнить что-то после выполнения promise. А catch отслеживает ошибки, которые могут проявиться в then. … В этой же статье, я увидел, что можно использовать либо then либо catch, цитирую

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

Это меня очень сильно запутало. Пожалуйста, помогите разобраться

  1. Далее… некоторые функции, находятся внутри Promise, а некоторые снаружи. Мне не понятно, как определить, оборачивать промисом функцию или писать её внутри? Прошу описать детальнее, что такое промис(как и то, как его вызывать). Насколько я понял, промисы необходимы для того, чтобы после выполнения, к примеру, запроса на сервер, мы могли бы с полученным ответом, что-то сделать, передав его в then. Также, промисы решают организационный вопрос, избавляя нас от цепочек колбэка и являются асинхронной функцией

  2. Рассмотрим пример.Предположим, что нам нужно реализовать две функции, одна должна загружать файл с сервера, а другая открыть данный файл. Понятно из задачи, что они должны идти синхронно. Сначала, должна выполниться первая и только после этого, запускаться вторая. Ниже, я приведу пример одной задачи и в комментариях задам некоторые уточняющие вопросы. Прошу на них ответить.

Промис - это объект который дает тебе описать как работать с данными которые еще не появились программе.

Например у тебя есть магазин. Фронтенде ты показываешь количество продуктов. Пусть это значение получается асинхронно (может 1 или несколько запросов). Тогда чтобы описать получение количества продуктов ты пишешь код вида, где возвращаешь Promise. В момент возврата промис еще не зарезолвлен (не resolved), нет значения прикрепленного к нему. Зато у него есть методы .then, .catch через которые потребиталь может описать функции которые выполнятся когда в промисе появится значение (устанавливается с помощью вызова resolve).

function getProductsCount() {
	return new Promise(function (resolve, reject) {
		//...

		//...
	})
}

А потребитель количества продуктов описать что он будет делать когда значение вычислится (когда промис зарезолвится). Именно эту мезанику я имею в виду когда говорю “объект который дает тебе описать как работать с данными которые еще не появились программе”.

getProductsCount().then(function (productCount) {
	$('.product-count').text(productCount)
})

Использование в ajax-е - не единственное место для промисов. Они нужны почти в любом асинхронном API: например на сервере промисы ипользуются для описания результатов вызовов базы данных, файловой системы. С помощью промисов удобно моделировать множественные запросы в виде одного промиса: это когда нужно сделать 5-10 запросов на сервер и работать с результатом всех запросов.


Разные задачи, но не взаимоисключающие. Ситуация именно такая как выглядит: с помощью catch ты описываешь только обработчик для ошибки. С помощью then ты описываешь обработчик для успеха а так же можешь описать обработчик для ошибки.


Ты всегда передаешь функции в promise чтобы он их контролировал. Воспро оборачивания не стоит, обращай внимание где функции передаются аргументами.

Ты передаешь функцию в промис, он контролирует когда ее вызвать. При ее вызове он контролирует какие функции передать в аргументы вызванной функции. Эти функции будут в переменных resolve, reject и уже ты определяешь когда их вызывать. Их вызов даст сигнал промису что или значение получено или ошибка

const p = new Promise(function (resolve, reject) {
    //....
})

И при использовании промиса через его методы ты тоже даешь функцию чтобы ее контролировал промис:

p.then(function (res) {
    console.log(res)
})

var isNetworkOK = true;
 
function downloadFile(url)  {      // Обязательно оборачивать в функцию Promise? Что это даёт?
// это дает возможность получить сколько угодно промисов, параметрезировав запрос по url.
// если не использовать функции то ты опишешь промис для 1 конкретного урла.
// с функцией ты можешь описать получение промисов для разных урлов (соответственно загрузку любого файла по урлу)
    console.log("Start downloading file ..."); // ***
 
    // A Promise
    var willIGetAFile = new Promise (
        function (resolve, reject) {
 
            if (isNetworkOK) {
                setTimeout( function() {
                    console.log("Complete the download process!"); // ***
                    var file = {
                        fileName: 'file.mp3',
                        fileContent: '...',
                        fileSize: '3 MB'
                    };
                    resolve(file); // resolve всегда должен возвращать объект?
// нет. Это может быть примитив: число, boolean итд.
                }, 5 * 1000); 
            } else {
                var error = new Error('There is a problem with the network.');
                reject(error); 
            }
        }
    );
    return willIGetAFile; // Зачем возвращать Promise? Разве он не был возвращён на 18 строке? ( resolve(file) )
// забудь на секунду про промисы и посмотри на синтаксические конструкции как на функции и объекты
// в какой строке функция downloadFile возвращает значение? (подсказка - не в 18). 
// теперь можешь вспомнить про промисы: return нужен чтобы результат работы функции был тем что справа от `return` - объект
// которым ниже потребитель будет пользоваться чтобы описать что делать с результатом. 

// в18 строке устанавлиается значение промиса, это не тоже самое что вернуть объект промиса
}
 
 // те же вопросы касаются и функции ниже
// те же ответы
function openFile(file) {
    console.log("Start opening file ..."); // ***
 
    var willFileOpen = new Promise(
        function (resolve, reject) {
             var message = "File " + file.fileName + " opened!"
             resolve(message);
        }
    );
 
    return willFileOpen;
}
 
console.log("Start app.."); // ***
 
// Call downloadFile(..) function:
// Returns a Promise object:
var willIGetAFile = downloadFile("http://example.com/file.mp3"); // Что здесь происходит? Мы что, перетираем промис на 7 строчке?

// мы записываем в willIGetAFile инстанс промиса (это у которого методы then и catch)
// этот объект представляет "обещаение" данных, и мы можем описать что мы хотим делать с данными если/когда они появятся.
 
 
willIGetAFile  // Здесь уже находится не промис, а downloadFile("http://example.com/file.mp3") И у него вызывается then... Почему?
// Здесь уже находится не промис, а downloadFile("http://example.com/file.mp3") - в чем разница?
// promise - это значение (вернее тип значения), downloadFile("http://example.com/file.mp3") - результат вызова функции, значение, значение типа промис.  Тут нет противоречия
        .then(openFile) // Chain it!
        .then(function (fulfilled) { // If successful fileOpen.
            // Get a message after file opened!
            // Output: File file.mp3 opened!
            console.log(fulfilled);
        })
        .catch(function (error) {
             // Network Error!
             // Output: There is a problem with the network.
             console.log(error.message);
        });
 
console.log("End app.."); // ***
2 симпатии

Спасибо за развёрнутый, но до конца не понимаю… Почему нельзя было написать так: downloadFile(“http://example.com/file.mp3”)
Зачем нужно было присваивать эту функцию в переменную, в которой лежит промис, который сам находится в этой функции…?

Как с таким подходом описать что нужно делать с файлом после загрузки?

так это функция запустить внутри себя promise

Верно. Где и как описать что делать с результатами загрузки. Если не писать код как он сейчас написан.

Я вас понимаю. Вы говорите о том, куда присвоит функция downloadFile(“http://example.com/file.mp3”) объект промис… Но почему тогда не использовать другую, свободную переменную? Ведь эта переменная занята!

function downloadFile(url)  {(соответственно загрузку любого файла по урлу)
    var willIGetAFile

willIGetAFile - эта переменная - локальная для функции downloadFile и никак не влияет на var willIGetAFile = downloadFile(“http://example.com/file.mp3”); переменную. Это просто совпадение что они имеют одинаковые имена. Так же можно было использовать переменную с любым другим именем.

Блин, точно…) Спасибо!

Я пометил свой первый ответ как принятый ответ на вопрос (можешь поменять если считаешь иначе). Пока можем продолжить выяснять нюансы по твоим вопросам.

1 симпатия

Промисы это альтернатива колбеку, с колбеком было так
test1 и test2 - некие асинхронные функции с колбеками

test1((result1) => {
  ...... что-то делаем
  test2((result2) => {
    ...... что-то делаем и возможно тут внутри у нас еще какие асинхронные функции
  })
})

в случае выше, велик риск попасть в колбекхел или как еще называют - лапшу, когда очень много вложенных функций и слева экрана у нас появиться огромный отступ. Такой код тяжело читать, что весьма важно.

Если же внутри test1 и test2 реализован промис, то это можно записать с колбеками но без постоянно возрастающих отступов

Promise.resolve().then(() => {
  return test1();
}).then((result1) => {
  ......
  retrun test2();
}).then((result2) => {
  ...... и так далее
}).catch(err => {
  ...... если же в асинхротронном потоке выше, где-то будет ошибка, она попадет в ближайший снизу catch, то-есть сюда
})

Помимо этого, так как колбеки это функции, а промисы все таки специальная вещь для работы с асинхронностью, у промисов куча ништяков для работы с асинхронностью

Ну а для тех кто не осилил промисы, и ситуаций когда надо надо просто дождаться результат асинхронной функции(промифицырованной), сделали еще и асинк авейты

внутри test1 и test2 реализован промис,

const run = async () => {
    const result1 = await test1().catch(err => { ..... });
    const result2 = await test2().catch(err => { ..... });
    ...... и так далее
});

или так

const run = async () => {
    try {
        const result1 = await test1().catch(err => { ..... });
        const result2 = await test2().catch(err => { ..... });
        ...... и так далее
    } catch (err) {
        .........
    }
});

Сама функция run при этом станет асинхронной и промифицырованной, но внутри можно делать псевдосинхронный код используя await

2 симпатии