Как вынести отдельно из методов then() или catch() функцию обработки отклонённого обещания (Promise Rejected) с поддержкой передачи нужного количества параметров при помощи bind()?
Что нужно запомнить?
Метод bind() всегда принимает первым параметром объект, в контексте которого будет вызвана функция.
Если мы просто указываем ссылку на функцию в методах then() или catch(), то эта функция просто примет первым (одним) параметром результат отклонения обещания. Остальные параметры функции будут undefined, если она (функция) ожидала их получить и с ними поработать. В общем по умолчанию 1 параметр. Если нужно передать не один параметр а несколько, тогда нужен bind().
Если мы в bind() передадим, кроме первого параметра нужное количество параметров для функции, тогда эта функция отработает корректно по своей задуманной логике.
Но если мы передадим в bind() недостаточное количество параметров, тогда результат из then() или catch() может попасть не в тот обработчик (не на своё место). Количество ожидаемых и переданных параметров может отличаться. Это влияет на расположение ожидаемого параметра, относительно добавляемых дополнительно в функцию.
По умолчанию обработчики отклонений в then() или catch() ждут ОДИН параметр!
Давайте перейдём к примерам, чтобы было понятнее.
Функция ждёт 3 параметра, но мы просто ссылаемся на неё — без bind()
let res = new Promise((resolve, reject)=>{ resolve(1) }); let rej1 = new Promise((resolve, reject)=>{ reject(0) }); let rej2 = new Promise((resolve, reject)=>{ reject('000') }); function f1(q,w,e){ console.log(`q`,q); console.log(`w`,w); console.log(`e`,e); }; Promise.all([res, rej2, rej1]).then(q=>q, f1); Promise.all([res, rej2, rej1]).catch(f1);
Лог консоли:
По итогу функция «f1» будет вызвана только с одним параметром — с «q«, который пришёл с результатом отклонения. Параметры «w» и «e» будут иметь значение undefined.
Важно помнить, что если отклонений в Promise.all() фактически много, то сработает самое первое и именно результат этого отклонения и будет передан. Остальные отклонения обещаний передаваться не будут.
Конкретно этот пример синтетический и мы стабильно получаем строку ‘000‘, но в реальном асинхронном мире может быть любое из отклонённых обещаний.
Функция ждёт 3 параметра, но мы указываем объект контекста вызова через bind() — не передаём ничего кроме this из результата отклонения
Эта ситуация будет равносильна предыдущей. Но мы её рассматриваем, чтобы наработать «насмотренность кода».
let res = new Promise((resolve, reject)=>{ resolve(1) }); let rej1 = new Promise((resolve, reject)=>{ reject(0) }); let rej2 = new Promise((resolve, reject)=>{ reject('000') }); function f1(q,w,e){ console.log(`q`,q); console.log(`w`,w) console.log(`e`,e) }; Promise.all([res, rej2, rej1]).then(q=>q, f1.bind(this)); Promise.all([res, rej2, rej1]).catch(f1.bind(this));
Лог консоли:
Метод bind() в любом случае первым параметром принимает объект, который будет использован функцией как свой this.
В момент вызова bind() мы передаём текущий this — это глобальный объект (window или global в зависимости клиент это или сервер). Но самих параметров для функции не передаём.
В результате в этом примере мы также вызовем «f1» с одним параметром.
Функция ждёт 3 параметра, но мы указываем объект контекста вызова через bind() — передаём this и один параметр
ВНИМАНИЕ! В этой ситуации мы заметим сдвиг параметров, который при первом просмотре может ввести в заблуждение. Здесь просто нужно очень хорошо понимать как работает сам bind(), then() и catch() со своими параметрами.
let res = new Promise((resolve, reject)=>{ resolve(1) }); let rej1 = new Promise((resolve, reject)=>{ reject(0) }); let rej2 = new Promise((resolve, reject)=>{ reject('000') }); function f1(q,w,e){ console.log(`q`,q); console.log(`w`,w) console.log(`e`,e) }; Promise.all([res, rej2, rej1]).then(q=>q, f1.bind(this, 'qqq')); Promise.all([res, rej2, rej1]).catch(f1.bind(this, 'qqq'));
Лог консоли:
В этот раз до функции «f1» дошли два параметра. Первым в неё пришёл тот, который был передан вторым в bind(). Поэтому мы видим в логе «q qqq«.
В идеале, то что мы хотим по факту передать в функцию, должно следовать со второго параметра в bind().
НО! Но если передаваемых параметров не хватает для функции, то метод then() или catch() подпихнёт туда свой результат отклонения последним параметром. В нашем случае не хватает ещё два параметра для функции. В результате then() или catch() подставляет результат отклонения обещания и вторым консольным логом мы получаем «w 000«.
Третьего параметра не приходит в функцию так как мы передали один, а then() или catch() уже отдал свой. Поэтому третий лог в консоль «e undefined«.
Важно! Ожидаемый параметр будет добавлен в самый конец из всех возможных переданных параметров!
Функция ждёт 3 параметра, но мы указываем объект контекста вызова через bind() — передаём this и два параметра
По аналогии с предыдущим.
let res = new Promise((resolve, reject)=>{ resolve(1) }); let rej1 = new Promise((resolve, reject)=>{ reject(0) }); let rej2 = new Promise((resolve, reject)=>{ reject('000') }); function f1(q,w,e){ console.log(`q`,q); console.log(`w`,w) console.log(`e`,e) }; Promise.all([res, rej2, rej1]).then(q=>q, f1.bind(this, 'qqq', 'www')); Promise.all([res, rej2, rej1]).catch(f1.bind(this, 'qqq', 'www'));
Лог консоли:
В этот раз мы пытаемся вызвать функцию с двумя параметрами, но then() или catch() подсовывает туда свой параметр и по итогу функция вызывается с тремя параметрами.
Функция ждёт 3 параметра, но мы указываем объект контекста вызова через bind() — передаём this и три параметра
Ну и наконец нужный нам вариант (опционально под задачу). Функция получает полный набор параметров для своего вызова, без сдвигов из-за then() или catch().
let res = new Promise((resolve, reject)=>{ resolve(1) }); let rej1 = new Promise((resolve, reject)=>{ reject(0) }); let rej2 = new Promise((resolve, reject)=>{ reject('000') }); function f1(q,w,e){ console.log(`q`,q); console.log(`w`,w) console.log(`e`,e) }; Promise.all([res, rej2, rej1]).then(q=>q, f1.bind(this, 'qqq', 'www', 'eee')); Promise.all([res, rej2, rej1]).catch(f1.bind(this, 'qqq', 'www', 'eee'));
Лог консоли:
В этой ситуации мы полностью вытеснили результат отклонения обещания. Ему просто не нашлось места в функции. Такое поведение может быть не применимо на практике, но нужно знать что оно возможно в JavaScript так в этом языке нет ограничения на передаваемые параметры в функцию.
Итог
Задачи могут быть разными. В этой публикации мы обсудили возможность вынесения функции из синтаксической конструкции методов then() и catch().
Мы это сделали для того, чтобы улучшить читаемость кода. Чтобы не нагромождать вложенность функций в функции. Чтобы функция обработки результата отклонения обещания могла принять не только сам результат отклонения, но и что-то ещё, из того что мы хотим.
При добавлении параметров для вызова через bind() ожидаемый функцией параметр будет в САМОМ КОНЦЕ, а не в начале, как может показаться.
Информационные ссылки
Стандарт ECMAScript мульти-страничная версия — https://tc39.es/ecma262/multipage/
Метод bind() — https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function.prototype.bind
Метод then() — https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.prototype.then
Метод catch() — https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-promise.prototype.catch