Promise all обработка ошибок

I have an array of Promises that I’m resolving with Promise.all(arrayOfPromises);

I go on to continue the promise chain. Looks something like this

existingPromiseChain = existingPromiseChain.then(function() {
  var arrayOfPromises = state.routes.map(function(route){
    return route.handler.promiseHandler();
  });
  return Promise.all(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
  // do stuff with my array of resolved promises, eventually ending with a res.send();
});

I want to add a catch statement to handle an individual promise in case it errors, but when I try, Promise.all returns the first error it finds (disregards the rest), and then I can’t get the data from the rest of the promises in the array (that didn’t error).

I’ve tried doing something like ..

existingPromiseChain = existingPromiseChain.then(function() {
      var arrayOfPromises = state.routes.map(function(route){
        return route.handler.promiseHandler()
          .then(function(data) {
             return data;
          })
          .catch(function(err) {
             return err
          });
      });
      return Promise.all(arrayOfPromises)
    });

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
      // do stuff with my array of resolved promises, eventually ending with a res.send();
});

But that doesn’t resolve.

Thanks!

Edit:

What the answers below said were completely true, the code was breaking due to other reasons. In case anyone is interested, this is the solution I ended up with …

Node Express Server Chain

serverSidePromiseChain
    .then(function(AppRouter) {
        var arrayOfPromises = state.routes.map(function(route) {
            return route.async();
        });
        Promise.all(arrayOfPromises)
            .catch(function(err) {
                // log that I have an error, return the entire array;
                console.log('A promise failed to resolve', err);
                return arrayOfPromises;
            })
            .then(function(arrayOfPromises) {
                // full array of resolved promises;
            })
    };

API Call (route.async call)

return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

Putting the .catch for Promise.all before the .then seems to have served the purpose of catching any errors from the original promises, but then returning the entire array to the next .then

Thanks!

Kamil Kiełczewski's user avatar

asked May 21, 2015 at 0:46

Jon's user avatar

6

Promise.all is all or nothing. It resolves once all promises in the array resolve, or reject as soon as one of them rejects. In other words, it either resolves with an array of all resolved values, or rejects with a single error.

Some libraries have something called Promise.when, which I understand would instead wait for all promises in the array to either resolve or reject, but I’m not familiar with it, and it’s not in ES6.

Your code

I agree with others here that your fix should work. It should resolve with an array that may contain a mix of successful values and errors objects. It’s unusual to pass error objects in the success-path but assuming your code is expecting them, I see no problem with it.

The only reason I can think of why it would «not resolve» is that it’s failing in code you’re not showing us and the reason you’re not seeing any error message about this is because this promise chain is not terminated with a final catch (as far as what you’re showing us anyway).

I’ve taken the liberty of factoring out the «existing chain» from your example and terminating the chain with a catch. This may not be right for you, but for people reading this, it’s important to always either return or terminate chains, or potential errors, even coding errors, will get hidden (which is what I suspect happened here):

Promise.all(state.routes.map(function(route) {
  return route.handler.promiseHandler().catch(function(err) {
    return err;
  });
}))
.then(function(arrayOfValuesOrErrors) {
  // handling of my array containing values and/or errors. 
})
.catch(function(err) {
  console.log(err.message); // some coding error in handling happened
});

answered May 21, 2015 at 15:27

jib's user avatar

jibjib

40.1k17 gold badges97 silver badges157 bronze badges

5

ES2020 introduces new method for the Promise type: Promise.allSettled().

Promise.allSettled gives you a signal when all the input promises are settled, which means they’re either fulfilled or rejected. This is useful in cases where you don’t care about the state of the promise, you just want to know when the work is done, regardless of whether it was successful.

async function() {
  const promises = [
    fetch('/api.stackexchange.com/2.2'), // succeeds
    fetch('/this-will-fail') // fails
  ];

  const result = await Promise.allSettled(promises);
  console.log(result.map(promise => promise.status));
  // ['fulfilled', 'rejected']
}

Read more in the v8 blog post.

answered Dec 21, 2019 at 13:13

MosheZada's user avatar

MosheZadaMosheZada

2,1091 gold badge14 silver badges17 bronze badges

1

To continue the Promise.all loop (even when a Promise rejects) I wrote a utility function which is called executeAllPromises. This utility function returns an object with results and errors.

The idea is that all Promises you pass to executeAllPromises will be wrapped into a new Promise which will always resolve. The new Promise resolves with an array which has 2 spots. The first spot holds the resolving value (if any) and the second spot keeps the error (if the wrapped Promise rejects).

As a final step the executeAllPromises accumulates all values of the wrapped promises and returns the final object with an array for results and an array for errors.

Here is the code:

function executeAllPromises(promises) {
  // Wrap all Promises in a Promise that will always "resolve"
  var resolvingPromises = promises.map(function(promise) {
    return new Promise(function(resolve) {
      var payload = new Array(2);
      promise.then(function(result) {
          payload[0] = result;
        })
        .catch(function(error) {
          payload[1] = error;
        })
        .then(function() {
          /* 
           * The wrapped Promise returns an array:
           * The first position in the array holds the result (if any)
           * The second position in the array holds the error (if any)
           */
          resolve(payload);
        });
    });
  });

  var errors = [];
  var results = [];

  // Execute all wrapped Promises
  return Promise.all(resolvingPromises)
    .then(function(items) {
      items.forEach(function(payload) {
        if (payload[1]) {
          errors.push(payload[1]);
        } else {
          results.push(payload[0]);
        }
      });

      return {
        errors: errors,
        results: results
      };
    });
}

var myPromises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.reject(new Error('3')),
  Promise.resolve(4),
  Promise.reject(new Error('5'))
];

executeAllPromises(myPromises).then(function(items) {
  // Result
  var errors = items.errors.map(function(error) {
    return error.message
  }).join(',');
  var results = items.results.join(',');
  
  console.log(`Executed all ${myPromises.length} Promises:`);
  console.log(`— ${items.results.length} Promises were successful: ${results}`);
  console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});

answered Jan 6, 2017 at 14:28

Benny Code's user avatar

Benny CodeBenny Code

50.7k28 gold badges230 silver badges198 bronze badges

1

Promise.allSettled

Instead of Promise.all use Promise.allSettled which waits for all promises to settle, regardless of the result

let p1 = new Promise(resolve => resolve("result1"));
let p2 = new Promise( (resolve,reject) => reject('some troubles') );
let p3 = new Promise(resolve => resolve("result3"));

// It returns info about each promise status and value
Promise.allSettled([p1,p2,p3]).then(result=> console.log(result));

Polyfill

answered Jun 29, 2020 at 9:00

Kamil Kiełczewski's user avatar

Kamil KiełczewskiKamil Kiełczewski

83.3k29 gold badges361 silver badges335 bronze badges

2

As @jib said,

Promise.all is all or nothing.

Though, you can control certain promises that are «allowed» to fail and we would like to proceed to .then.

For example.

  Promise.all([
    doMustAsyncTask1,
    doMustAsyncTask2,
    doOptionalAsyncTask
    .catch(err => {
      if( /* err non-critical */) {
        return
      }
      // if critical then fail
      throw err
    })
  ])
  .then(([ mustRes1, mustRes2, optionalRes ]) => {
    // proceed to work with results
  })

answered Feb 14, 2019 at 21:08

Herman's user avatar

HermanHerman

1911 silver badge5 bronze badges

Using Async await —

here one async function func1 is returning a resolved value, and func2 is throwing a error and returning a null in this situation, we can handle it how we want and return accordingly.

const callingFunction  = async () => {
    const manyPromises = await Promise.all([func1(), func2()]);
    console.log(manyPromises);
}


const func1 = async () => {
    return 'func1'
}

const func2 = async () => {
    try {
        let x;
        if (!x) throw "x value not present"
    } catch(err) {
       return null
    }
}

callingFunction();

Output is — [ ‘func1’, null ]

answered Mar 18, 2019 at 7:54

Nayan Patel's user avatar

Nayan PatelNayan Patel

1,67525 silver badges27 bronze badges

0

if you get to use the q library https://github.com/kriskowal/q
it has q.allSettled() method that can solve this problem
you can handle every promise depending on its state either fullfiled or rejected
so

existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
  return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
   if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
     //do somthing
   } else {
     // do something else
   }
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});

answered Jun 19, 2016 at 14:36

Mohamed Mahmoud's user avatar

3

Promise.allSettled with a filter

const promises = [
  fetch('/api-call-1'),
  fetch('/api-call-2'),
  fetch('/api-call-3'),
];
// Imagine some of these requests fail, and some succeed.

const resultFilter = (result, error) => result.filter(i => i.status === (!error ? 'fulfilled' : 'rejected')).map(i => (!error ? i.value : i.reason));

const result = await Promise.allSettled(promises);

const fulfilled = resultFilter(result); // all fulfilled results
const rejected = resultFilter(result, true); // all rejected results

answered Apr 27, 2021 at 12:57

Asaf's user avatar

AsafAsaf

9091 gold badge12 silver badges14 bronze badges

For those using ES8 that stumble here, you can do something like the following, using async functions:

var arrayOfPromises = state.routes.map(async function(route){
  try {
    return await route.handler.promiseHandler();
  } catch(e) {
    // Do something to handle the error.
    // Errored promises will return whatever you return here (undefined if you don't return anything).
  }
});

var resolvedPromises = await Promise.all(arrayOfPromises);

answered Sep 17, 2018 at 22:32

Tyler Yasaka's user avatar

Have you considered Promise.prototype.finally()?

It seems to be designed to do exactly what you want — execute a function once all the promises have settled (resolved/rejected), regardless of some of the promises being rejected.

From the MDN documentation:

The finally() method can be useful if you want to do some processing or cleanup once the promise is settled, regardless of its outcome.

The finally() method is very similar to calling .then(onFinally, onFinally) however there are couple of differences:

When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it.

A finally callback will not receive any argument, since there’s no reliable means of determining if the promise was fulfilled or rejected. This use case is for precisely when you do not care about the rejection reason, or the fulfillment value, and so there’s no need to provide it.

Unlike Promise.resolve(2).then(() => {}, () => {}) (which will be resolved with undefined), Promise.resolve(2).finally(() => {}) will be resolved with 2.
Similarly, unlike Promise.reject(3).then(() => {}, () => {}) (which will be fulfilled with undefined), Promise.reject(3).finally(() => {}) will be rejected with 3.

== Fallback ==

If your version of JavaScript doesn’t support Promise.prototype.finally() you can use this workaround from Jake Archibald: Promise.all(promises.map(p => p.catch(() => undefined)));

answered Dec 13, 2018 at 0:08

Tom Auger's user avatar

Tom AugerTom Auger

19.3k22 gold badges80 silver badges104 bronze badges

6

We can handle the rejection at the individual promises level, so when we get the results in our result array, the array index which has been rejected will be undefined. We can handle that situation as needed, and use the remaining results.

Here I have rejected the first promise, so it comes as undefined, but we can use the result of the second promise, which is at index 1.

const manyPromises = Promise.all([func1(), func2()]).then(result => {
    console.log(result[0]);  // undefined
    console.log(result[1]);  // func2
});

function func1() {
    return new Promise( (res, rej) => rej('func1')).catch(err => {
        console.log('error handled', err);
    });
}

function func2() {
    return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}

Dan Dascalescu's user avatar

answered Sep 3, 2018 at 14:47

Nayan Patel's user avatar

Nayan PatelNayan Patel

1,67525 silver badges27 bronze badges

2

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];
let sum = 0;
let promiseErrorArr = [];

Promise.allSettled(promises)
.then((results) => {
          results.forEach(result => {
            if (result.status === "rejected") {
              sum += 1;
              promiseErrorArr.push(result)
            }
          })
    return ( (sum>0) ? promiseFailed() : promisePassed())
})

function promiseFailed(){
  console.log('one or all failed!')
  console.log(promiseErrorArr)
}

function promisePassed(){
  console.log('all passed!')
}

// expected output:
// "one or all failed!"
// Array [Object { status: "rejected", reason: "foo" }]

answered May 10, 2021 at 19:40

Joey's user avatar

JoeyJoey

4911 gold badge5 silver badges14 bronze badges

With the help of allSettled,we can now read the status of
each promise
is, and process each error individually, without losing any of this critical information

const promises = [
    fetch('/api/first'), // first
    fetch('/api/second') // second
];

The simplest way is to handle errors

const [firstResult, secondResult] = await Promise.allSettled(promises)


 // Process first
 if (firstResult.status === 'rejected') {
   const err = firstResult.reason
   // Here you can handle error
 } else {
   const first = firstResult.value
 }

  // Process second
 if (secondResult.status === 'rejected') {
   const err = secondResult.reason
   // Here you can handle error
 } else {
   const second = secondResult.value
 }

A nice way to handle error

const results = await Promise.allSettled(promises);
const [first, second] = handleResults(results)


function handleResults(results) {
  const errors = results.filter(result => result.status === 'rejected').map(result => result.reason)

  if (errors.length) {
    // Aggregate all errors into one
    throw new AggregateError(errors)
  }

  return results.map(result => result.value)
}

answered Jan 26 at 7:39

Abolfazl Roshanzamir's user avatar

Alternately, if you have a case where you don’t particularly care about the values of the resolved promises when there is one failure but you still want them to have run, you could do something like this which will resolve with the promises as normal when they all succeed and reject with the failed promises when any of them fail:

function promiseNoReallyAll (promises) {
  return new Promise(
    async (resolve, reject) => {
      const failedPromises = []

      const successfulPromises = await Promise.all(
        promises.map(
          promise => promise.catch(error => {
            failedPromises.push(error)
          })
        )
      )

      if (failedPromises.length) {
        reject(failedPromises)
      } else {
        resolve(successfulPromises)
      }
    }
  )
}

answered Apr 30, 2019 at 21:44

Eric's user avatar

EricEric

9992 gold badges8 silver badges23 bronze badges

You can always wrap your promise returning functions in a way that they catches failure and returning instead an agreed value (e.g. error.message), so the exception won’t roll all the way up to the Promise.all function and disable it.

async function resetCache(ip) {

    try {

        const response = await axios.get(`http://${ip}/resetcache`);
        return response;

    }catch (e) {

        return {status: 'failure', reason: 'e.message'};
    }

}

answered Jul 15, 2019 at 7:37

Tamir Nakar's user avatar

Tamir NakarTamir Nakar

9031 gold badge10 silver badges17 bronze badges

I’ve found a way (workaround) to do this without making it sync.

So as it was mentioned before Promise.all is all of none.

so… Use an enclosing promise to catch and force resolve.


      let safePromises = originalPrmises.map((imageObject) => {
            return new Promise((resolve) => {
              // Do something error friendly
              promise.then(_res => resolve(res)).catch(_err => resolve(err))
            })
        })
    })

    // safe
    return Promise.all(safePromises)

answered Nov 17, 2019 at 18:37

Juan Sebastian Contreras Aceve's user avatar

You would need to know how to identify an error in your results. If you do not have a standard expected error, I suggest that you run a transformation on each error in the catch block that makes it identifiable in your results.

try {
  let resArray = await Promise.all(
    state.routes.map(route => route.handler.promiseHandler().catch(e => e))
  );

  // in catch(e => e) you can transform your error to a type or object
  // that makes it easier for you to identify whats an error in resArray
  // e.g. if you expect your err objects to have e.type, you can filter
  // all errors in the array eg
  // let errResponse = resArray.filter(d => d && d.type === '<expected type>')
  // let notNullResponse = resArray.filter(d => d)

  } catch (err) {
    // code related errors
  }

answered Nov 19, 2019 at 0:54

Anthony Awuley's user avatar

Not the best way to error log, but you can always set everything to an array for the promiseAll, and store the resulting results into new variables.

If you use graphQL you need to postprocess the response regardless and if it doesn’t find the correct reference it’ll crash the app, narrowing down where the problem is at

const results = await Promise.all([
  this.props.client.query({
    query: GET_SPECIAL_DATES,
  }),
  this.props.client.query({
    query: GET_SPECIAL_DATE_TYPES,
  }),
  this.props.client.query({
    query: GET_ORDER_DATES,
  }),
]).catch(e=>console.log(e,"error"));
const specialDates = results[0].data.specialDates;
const specialDateTypes = results[1].data.specialDateTypes;
const orderDates = results[2].data.orders;

answered Dec 10, 2019 at 15:46

Vincent Tang's user avatar

Vincent TangVincent Tang

3,6486 gold badges44 silver badges61 bronze badges

Unfortunately, I don’t have enough reputation to comment (or do much of anything, really), so I’m posting this as an answer in response to Eric’s answer here.

The executor function can also be an async function. However, this is usually a mistake, for a few reasons:

  • If an async executor function throws an error, the error will be lost and won’t cause the newly-constructed Promise to reject. This could make it difficult to debug and handle some errors.
  • If a Promise executor function is using await, this is usually a sign that it is not actually necessary to use the new Promise constructor, or the scope of the new Promise constructor can be reduced.

From this explanation as to why Promises should not utilize an async executor function

Instead, you should opt for Promise.allSettled(), as suggested here by Asaf.

answered Dec 26, 2022 at 20:43

jerichaux's user avatar

That’s how Promise.all is designed to work. If a single promise reject()‘s, the entire method immediately fails.

There are use cases where one might want to have the Promise.all allowing for promises to fail. To make this happen, simply don’t use any reject() statements in your promise. However, to ensure your app/script does not freeze in case any single underlying promise never gets a response, you need to put a timeout on it.

function getThing(uid,branch){
    return new Promise(function (resolve, reject) {
        xhr.get().then(function(res) {
            if (res) {
                resolve(res);
            } 
            else {
                resolve(null);
            }
            setTimeout(function(){reject('timeout')},10000)
        }).catch(function(error) {
            resolve(null);
        });
    });
}

answered Jun 10, 2018 at 4:37

Ronnie Royston's user avatar

Ronnie RoystonRonnie Royston

16.3k6 gold badges74 silver badges90 bronze badges

2

I wrote a npm library to deal with this problem more beautiful.
https://github.com/wenshin/promiseallend

Install

npm i --save promiseallend

2017-02-25 new api, it’s not break promise principles

const promiseAllEnd = require('promiseallend');

const promises = [Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)];
const promisesObj = {k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)};

// input promises with array
promiseAllEnd(promises, {
    unhandledRejection(error, index) {
        // error is the original error which is 'error'.
        // index is the index of array, it's a number.
        console.log(error, index);
    }
})
    // will call, data is `[1, undefined, 2]`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// input promises with object
promiseAllEnd(promisesObj, {
    unhandledRejection(error, prop) {
        // error is the original error.
        // key is the property of object.
        console.log(error, prop);
    }
})
    // will call, data is `{k1: 1, k3: 2}`
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

// the same to `Promise.all`
promiseAllEnd(promises, {requireConfig: true})
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [false, true, false]})
    // won't call
    .then(data => console.log(data))
    // will call, `error.detail` is 'error', `error.key` is number 1.
    .catch(error => console.log(error.detail))

// requireConfig is Array
promiseAllEnd(promises, {requireConfig: [true, false, false]})
    // will call, data is `[1, undefined, 2]`.
    .then(data => console.log(data))
    // won't call
    .catch(error => console.log(error.detail))

————————————————————————————————

Old bad api, do not use it!

let promiseAllEnd = require('promiseallend');

// input promises with array
promiseAllEnd([Promise.resolve(1), Promise.reject('error'), Promise.resolve(2)])
    .then(data => console.log(data)) // [1, undefined, 2]
    .catch(error => console.log(error.errorsByKey)) // {1: 'error'}

// input promises with object
promiseAllEnd({k1: Promise.resolve(1), k2: Promise.reject('error'), k3: Promise.resolve(2)})
    .then(data => console.log(data)) // {k1: 1, k3: 2}
    .catch(error => console.log(error.errorsByKey)) // {k2: 'error'}

answered May 15, 2016 at 6:48

wenshin's user avatar

wenshinwenshin

1671 silver badge6 bronze badges

6

Время на прочтение
8 мин

Количество просмотров 84K

Доброго времени суток, Хабр! Представляю вашему вниманию перевод статьи «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) Использование промиса. Большую часть времени вы будете пользоваться промисами, нежели создавать их, но важно знать как они создаются.

Вот и все, надеюсь эта статья оказалась для вас полезной!

Promise API

В классе Promise есть 6 статических методов. Давайте познакомимся с ними.

Promise.all

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

Например, параллельно загрузить несколько файлов и обработать результат, когда он готов.

Для этого как раз и пригодится Promise.all.

Синтаксис:

let promise = Promise.all(iterable);

Метод Promise.all принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.

Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.

Например, Promise.all, представленный ниже, выполнится спустя 3 секунды, его результатом будет массив [1, 2, 3]:

Promise.all([
  new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1
  new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2
  new Promise(resolve => setTimeout(() => resolve(3), 1000))  // 3
]).then(alert); // когда все промисы выполнятся, результат будет 1,2,3
// каждый промис даёт элемент массива

Обратите внимание, что порядок элементов массива в точности соответствует порядку исходных промисов. Даже если первый промис будет выполняться дольше всех, его результат всё равно будет первым в массиве.

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

Например, если у нас есть массив ссылок, то мы можем загрузить их вот так:

let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://api.github.com/users/jeresig'
];

// Преобразуем каждый URL в промис, возвращённый fetch
let requests = urls.map(url => fetch(url));

// Promise.all будет ожидать выполнения всех промисов
Promise.all(requests)
  .then(responses => responses.forEach(
    response => alert(`${response.url}: ${response.status}`)
  ));

А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же):

let names = ['iliakan', 'remy', 'jeresig'];

let requests = names.map(name => fetch(`https://api.github.com/users/${name}`));

Promise.all(requests)
  .then(responses => {
    // все промисы успешно завершены
    for(let response of responses) {
      alert(`${response.url}: ${response.status}`); // покажет 200 для каждой ссылки
    }

    return responses;
  })
  // преобразовать массив ответов response в response.json(),
  // чтобы прочитать содержимое каждого
  .then(responses => Promise.all(responses.map(r => r.json())))
  // все JSON-ответы обработаны, users - массив с результатами
  .then(users => users.forEach(user => alert(user.name)));

Если любой из промисов завершится с ошибкой, то промис, возвращённый Promise.all, немедленно завершается с этой ошибкой.

Например:

Promise.all([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
*!*
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)),
*/!*
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Ошибка!

Здесь второй промис завершится с ошибкой через 2 секунды. Это приведёт к немедленной ошибке в Promise.all, так что выполнится .catch: ошибка этого промиса становится ошибкой всего Promise.all.

Если один промис завершается с ошибкой, то весь `Promise.all` завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются.

Например, если сделано несколько вызовов `fetch`, как в примере выше, и один не прошёл, то остальные будут всё ещё выполняться, но `Promise.all` за ними уже не смотрит. Скорее всего, они так или иначе завершатся, но их результаты будут проигнорированы.

`Promise.all` ничего не делает для их отмены, так как в промисах вообще нет концепции "отмены". В главе <info:fetch-abort> мы рассмотрим `AbortController`, который помогает с этим, но он не является частью Promise API.

««smart header=»Promise.all(iterable) разрешает передавать не-промисы в `iterable` (перебираемом объекте)»
Обычно, `Promise.all(…)` принимает перебираемый объект промисов (чаще всего массив). Но если любой из этих объектов не является промисом, он передаётся в итоговый массив «как есть».

Например, здесь результат: [1, 2, 3]

Promise.all([
  new Promise((resolve, reject) => {
    setTimeout(() => resolve(1), 1000)
  }),
  2,
  3  
]).then(alert); // 1, 2, 3

Таким образом, мы можем передавать уже готовые значения, которые не являются промисами, в Promise.all, иногда это бывает удобно.


## Promise.allSettled

[recent browser="new"]

Синтаксис:

```js
let promise = Promise.allSettled(iterable);
```

`Promise.all` завершается с ошибкой, если она возникает в любом из переданных промисов. Это подходит для ситуаций "всё или ничего", когда нам нужны *все* результаты для продолжения:

```js
Promise.all([
  fetch('/template.html'),
  fetch('/style.css'),
  fetch('/data.json')
]).then(render); // методу render нужны результаты всех fetch
```

Метод `Promise.allSettled` всегда ждёт завершения всех промисов. В массиве результатов будет

- `{status:"fulfilled", value:результат}` для успешных завершений,
- `{status:"rejected", reason:ошибка}` для ошибок.

Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные.

Используем для этого `Promise.allSettled`:

```js run
let urls = [
  'https://api.github.com/users/iliakan',
  'https://api.github.com/users/remy',
  'https://no-such-url'
];

Promise.allSettled(urls.map(url => fetch(url)))
  .then(results => { // (*)
    results.forEach((result, num) => {
      if (result.status == "fulfilled") {
        alert(`${urls[num]}: ${result.value.status}`);
      }
      if (result.status == "rejected") {
        alert(`${urls[num]}: ${result.reason}`);
      }
    });
  });
```

Массив `results` в строке `(*)` будет таким:
```js
[
  {status: 'fulfilled', value: ...объект ответа...},
  {status: 'fulfilled', value: ...объект ответа...},
  {status: 'rejected', reason: ...объект ошибки...}
]
```

То есть, для каждого промиса у нас есть его статус и значение/ошибка.

### Полифил

Если браузер не поддерживает `Promise.allSettled`, для него легко сделать полифил:

```js
if(!Promise.allSettled) {
  Promise.allSettled = function(promises) {
    return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({
      status: 'fulfilled',
      value: value
    }), error => ({
      status: 'rejected',
      reason: error
    }))));
  };
}
```

В этом коде `promises.map` берёт аргументы, превращает их в промисы (на всякий случай) и добавляет каждому обработчик `.then`.

Этот обработчик превращает успешный результат `value` в `{state:'fulfilled', value: value}`, а ошибку `error` в `{state:'rejected', reason: error}`. Это как раз и есть формат результатов `Promise.allSettled`.

Затем мы можем использовать `Promise.allSettled`, чтобы получить результаты *всех* промисов, даже если при выполнении какого-то возникнет ошибка.

## Promise.race

Метод очень похож на `Promise.all`, но ждёт только первый *выполненный* промис, из которого берёт результат (или ошибку).

Синтаксис:

```js
let promise = Promise.race(iterable);
```

Например, тут результат будет `1`:

```js run
Promise.race([
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
```

Быстрее всех выполнился первый промис, он и дал результат. После этого остальные промисы игнорируются.

## Promise.any

Метод очень похож на `Promise.race`, но ждёт только первый *успешно выполненный* промис, из которого берёт результат. 

Если ни один из переданных промисов не завершится успешно, тогда возвращённый объект Promise будет отклонён с помощью `AggregateError` – специального объекта ошибок, который хранит все ошибки промисов в своём свойстве `errors`.

Синтаксис:

```js
let promise = Promise.any(iterable);
```

Например, здесь, результатом будет `1`:

```js run
Promise.any([
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 1000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
  new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
```

Первый промис в этом примере был самым быстрым, но он был отклонён, поэтому результатом стал второй. После того, как первый успешно выполненный промис "выиграет гонку", все дальнейшие результаты будут проигнорированы.

Вот пример, в котором все промисы отклоняются:

```js run
Promise.any([
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 1000)),
  new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ещё одна ошибка!")), 2000))
]).catch(error => {
  console.log(error.constructor.name); // AggregateError
  console.log(error.errors[0]); // Error: Ошибка!
  console.log(error.errors[1]); // Error: Ещё одна ошибка!
});
```

Как вы можете видеть, объекты ошибок для отклонённых промисов доступны в свойстве `errors` объекта `AggregateError`.

## Promise.resolve/reject

Методы `Promise.resolve` и `Promise.reject` редко используются в современном коде, так как синтаксис `async/await` (мы рассмотрим его [чуть позже](info:async-await)) делает их, в общем-то, не нужными.

Мы рассмотрим их здесь для полноты картины, а также для тех, кто по каким-то причинам не может использовать `async/await`.

### Promise.resolve

- `Promise.resolve(value)` создаёт успешно выполненный промис с результатом `value`.

То же самое, что:

```js
let promise = new Promise(resolve => resolve(value));
```

Этот метод используют для совместимости: когда ожидается, что функция возвратит именно промис.

Например, функция `loadCached` ниже загружает URL и запоминает (кеширует) его содержимое. При будущих вызовах с тем же URL он тут же читает предыдущее содержимое из кеша, но использует `Promise.resolve`, чтобы сделать из него промис, для того, чтобы возвращаемое значение всегда было промисом:

```js
let cache = new Map();

function loadCached(url) {
  if (cache.has(url)) {
*!*
    return Promise.resolve(cache.get(url)); // (*)
*/!*
  }

  return fetch(url)
    .then(response => response.text())
    .then(text => {
      cache.set(url,text);
      return text;
    });
}
```

Мы можем писать `loadCached(url).then(…)`, потому что функция `loadCached` всегда возвращает промис. Мы всегда можем использовать `.then` после `loadCached`. Это и есть цель использования `Promise.resolve` в строке `(*)`.

### Promise.reject

- `Promise.reject(error)` создаёт промис, завершённый с ошибкой `error`.

То же самое, что:

```js
let promise = new Promise((resolve, reject) => reject(error));
```

На практике этот метод почти никогда не используется.

## Итого

Мы ознакомились с шестью статическими методами класса `Promise`:

1. `Promise.all(promises)` -- ожидает выполнения всех промисов и возвращает массив с результатами. Если любой из указанных промисов вернёт ошибку, то результатом работы `Promise.all` будет эта ошибка, результаты остальных промисов будут игнорироваться.
2. `Promise.allSettled(promises)` (добавлен недавно) -- ждёт, пока все промисы завершатся и возвращает их результаты в виде массива с объектами, у каждого объекта два свойства:
    - `status`: `"fulfilled"`, если выполнен успешно или `"rejected"`, если ошибка,
    - `value` - результат, если успешно или `reason` - ошибка, если нет.
3. `Promise.race(promises)` -- ожидает первый *выполненный* промис, который становится его результатом, остальные игнорируются.
4. `Promise.any(promises)` (добавлен недавно) -- ожидает первый *успешно выполненный* промис, который становится его результатом, остальные игнорируются. Если все переданные промисы отклонены, [`AggregateError`](mdn:js/AggregateError) становится ошибкой `Promise.any`.
5. `Promise.resolve(value)` -- возвращает успешно выполнившийся промис с результатом `value`.
6. `Promise.reject(error)` -- возвращает промис с ошибкой `error`.

Из всех перечисленных методов, самый часто используемый - это, пожалуй, `Promise.all`.

JavaScript Promise Tutorial – How to Resolve or Reject Promises in JS

Promises are important building blocks for asynchronous operations in JavaScript. You may think that promises are not so easy to understand, learn, and work with. And trust me, you are not alone!

Promises are challenging for many web developers, even after spending years working with them.

In this article, I want to try to change that perception while sharing what I’ve learned about JavaScript Promises over the last few years. Hope you find it useful.

What is a Promise in JavaScript?

A Promise is a special JavaScript object. It produces a value after an asynchronous (aka, async) operation completes successfully, or an error if it does not complete successfully due to time out, network error, and so on.

Successful call completions are indicated by the resolve function call, and errors are indicated by the reject function call.

You can create a promise using the promise constructor like this:

let promise = new Promise(function(resolve, reject) {    
    // Make an asynchronous call and either resolve or reject
});

In most cases, a promise may be used for an asynchronous operation. However, technically, you can resolve/reject on both synchronous and asynchronous operations.

Hang on, don’t we have callback functions for async operations?

Oh, yes! That’s right. We have callback functions in JavaScript. But, a callback is not a special thing in JavaScript. It is a regular function that produces results after an asynchronous call completes (with success/error).

The word ‘asynchronous’ means that something happens in the future, not right now. Usually, callbacks are only used when doing things like network calls, or uploading/downloading things, talking to databases, and so on.

While callbacks are helpful, there is a huge downside to them as well. At times, we may have one callback inside another callback that’s in yet another callback and so on. I’m serious! Let’s understand this «callback hell» with an example.

How to Avoid Callback Hell – PizzaHub Example

Let’s order a Veg Margherita pizza 🍕 from the PizzaHub. When we place the order, PizzaHub automatically detects our location, finds a nearby pizza restaurant, and finds if the pizza we are asking for is available.

If it’s available, it detects what kind of beverages we get for free along with the pizza, and finally, it places the order.

If the order is placed successfully, we get a message with a confirmation.

So how do we code this using callback functions? I came up with something like this:

function orderPizza(type, name) {
    
    // Query the pizzahub for a store
    query(`/api/pizzahub/`, function(result, error){
       if (!error) {
           let shopId = result.shopId;
           
           // Get the store and query pizzas
           query(`/api/pizzahub/pizza/${shopid}`, function(result, error){
               if (!error) {
                   let pizzas = result.pizzas;
                   
                   // Find if my pizza is availavle
                   let myPizza = pizzas.find((pizza) => {
                       return (pizza.type===type && pizza.name===name);
                   });
                   
                   // Check for the free beverages
                   query(`/api/pizzahub/beverages/${myPizza.id}`, function(result, error){
                       if (!error) {
                           let beverage = result.id;
                           
                           // Prepare an order
                           query(`/api/order`, {'type': type, 'name': name, 'beverage': beverage}, function(result, error){
                              if (!error) {
                                  console.log(`Your order of ${type} ${name} with ${beverage} has been placed`);
                              } else {
                                  console.log(`Bad luck, No Pizza for you today!`);
                              }
                           });

                       }
                   })
               }
           });
       } 
    });
}

// Call the orderPizza method
orderPizza('veg', 'margherita');

Let’s have a close look at the orderPizza function in the above code.

It calls an API to get your nearby pizza shop’s id. After that, it gets the list of pizzas available in that restaurant. It checks if the pizza we are asking for is found and makes another API call to find the beverages for that pizza. Finally the order API places the order.

Here we use a callback for each of the API calls. This leads us to use another callback inside the previous, and so on.

This means we get into something we call (very expressively) Callback Hell. And who wants that? It also forms a code pyramid which is not only confusing but also error-prone.

callback-hell

Demonstration of callback hell and pyramid

There are a few ways to come out of (or not get into) callback hell. The most common one is by using a Promise or async function. However, to understand async functions well, you need to have a fair understanding of Promises first.

So let’s get started and dive into promises.

Understanding Promise States

Just to review, a promise can be created with the constructor syntax, like this:

let promise = new Promise(function(resolve, reject) {
  // Code to execute
});

The constructor function takes a function as an argument. This function is called the executor function.

// Executor function passed to the 
// Promise constructor as an argument
function(resolve, reject) {
    // Your logic goes here...
}

The executor function takes two arguments, resolve and reject. These are the callbacks provided by the JavaScript language. Your logic goes inside the executor function that runs automatically when a new Promise is created.

For the promise to be effective, the executor function should call either of the callback functions, resolve or reject. We will learn more about this in detail in a while.

The new Promise() constructor returns a promise object. As the executor function needs to handle async operations, the returned promise object should be capable of informing when the execution has been started, completed (resolved) or retuned with error (rejected).

A promise object has the following internal properties:

  1. state – This property can have the following values:
  • pending: Initially when the executor function starts the execution.
  • fulfilled: When the promise is resolved.
  • rejected: When the promise is rejected.

states_1

Promise states

2.  result – This property can have the following values:

  • undefined: Initially when the state value is pending.
  • value: When resolve(value) is called.
  • error: When reject(error) is called.

These internal properties are code-inaccessible but they are inspectable. This means that we will be able to inspect the state and result property values using the debugger tool, but we will not be able to access them directly using the program.

promise_state_inspect

Able to inspect the internal properties of a promise

A promise’s state can be pending, fulfilled or rejected. A promise that is either resolved or rejected is called settled.

states_2

A settled promise is either fulfilled or rejected

How promises are resolved and rejected

Here is an example of a promise that will be resolved (fulfilled state) with the value I am done immediately.

let promise = new Promise(function(resolve, reject) {
    resolve("I am done");
});

The promise below will be rejected (rejected state) with the error message Something is not right!.

let promise = new Promise(function(resolve, reject) {
    reject(new Error('Something is not right!'));
});

An important point to note:

A Promise executor should call only one resolve or one reject. Once one state is changed (pending => fulfilled or pending => rejected), that’s all. Any further calls to resolve or reject will be ignored.

let promise = new Promise(function(resolve, reject) {
  resolve("I am surely going to get resolved!");

  reject(new Error('Will this be ignored?')); // ignored
  resolve("Ignored?"); // ignored
});

In the example above, only the first one to resolve will be called and the rest will be ignored.

How to handle a Promise once you’ve created it

A Promise uses an executor function to complete a task (mostly asynchronously). A consumer function (that uses an outcome of the promise) should get notified when the executor function is done with either resolving (success) or rejecting (error).

The handler methods, .then(), .catch() and .finally(), help to create the link between the executor and the consumer functions so that they can be in sync when a promise resolves or rejects.

consumer_executor

The executor and consumer functions

How to Use the .then() Promise Handler

The .then() method should be called on the promise object to handle a result (resolve) or an error (reject).

It accepts two functions as parameters. Usually, the .then() method should be called from the consumer function where you would like to know the outcome of a promise’s execution.

promise.then(
  (result) => { 
     console.log(result);
  },
  (error) => { 
     console.log(error);
  }
);

If you are interested only in successful outcomes, you can just pass one argument to it, like this:

promise.then(
  (result) => { 
      console.log(result);
  }
);

If you are interested only in the error outcome, you can pass null for the first argument, like this:

promise.then(
  null,
  (error) => { 
      console.log(error)
  }
);

However, you can handle errors in a better way using the .catch() method that we will see in a minute.

Let’s look at a couple of examples of handling results and errors using the .then and .catch handlers. We will make this learning a bit more fun with a few real asynchronous requests. We will use the PokeAPI to get information about Pokémon and resolve/reject them using Promises.

First, let us create a generic function that accepts a PokeAPI URL as argument and returns a Promise. If the API call is successful, a resolved promise is returned. A rejected promise is returned for any kind of errors.

We will be using this function in several examples from now on to get a promise and work on it.

function getPromise(URL) {
  let promise = new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest();
    req.open("GET", URL);
    req.onload = function () {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject("There is an Error!");
      }
    };
    req.send();
  });
  return promise;
}
Utility method to get a Promise

Example 1: Get 50 Pokémon’s information:

const ALL_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon?limit=50';

// We have discussed this function already!
let promise = getPromise(ALL_POKEMONS_URL);

const consumer = () => {
    promise.then(
        (result) => {
            console.log({result}); // Log the result of 50 Pokemons
        },
        (error) => {
            // As the URL is a valid one, this will not be called.
            console.log('We have encountered an Error!'); // Log an error
    });
}

consumer();

Example 2: Let’s try an invalid URL

const POKEMONS_BAD_URL = 'https://pokeapi.co/api/v2/pokemon-bad/';

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.then(
        (result) => {
            // The promise didn't resolve. Hence, it will
            // not be executed.
            console.log({result});
        },
        (error) => {
            // A rejected prmise will execute this
            console.log('We have encountered an Error!'); // Log an error
        }
    );
}

consumer();

How to Use the .catch() Promise Handler

You can use this handler method to handle errors (rejections) from promises. The syntax of passing null as the first argument to the .then() is not a great way to handle errors. So we have .catch() to do the same job with some neat syntax:

// This will reject as the URL is 404
let promise = getPromise(POKEMONS_BAD_URL);

const consumer = () => {
    promise.catch(error => console.log(error));
}

consumer();

If we throw an Error like new Error("Something wrong!")  instead of calling the reject from the promise executor and handlers, it will still be treated as a rejection. It means that this will be caught by the .catch handler method.

This is the same for any synchronous exceptions that happen in the promise executor and handler functions.

Here is an example where it will be treated like a reject and the .catch handler method will be called:

new Promise((resolve, reject) => {
  throw new Error("Something is wrong!");// No reject call
}).catch((error) => console.log(error)); 

How to Use the .finally() Promise Handler

The .finally() handler performs cleanups like stopping a loader, closing a live connection, and so on. The finally() method will be called irrespective of whether a promise resolves or rejects. It passes through the result or error to the next handler which can call a .then() or .catch() again.

Here is an example that’ll help you understand all three methods together:

let loading = true;
loading && console.log('Loading...');

// Gatting Promise
promise = getPromise(ALL_POKEMONS_URL);

promise.finally(() => {
    loading = false;
    console.log(`Promise Settled and loading is ${loading}`);
}).then((result) => {
    console.log({result});
}).catch((error) => {
    console.log(error)
});

To explain a bit further:

  • The .finally() method makes loading false.
  • If the promise resolves, the .then() method will be called. If the promise rejects with an error, the .catch() method will be called. The .finally() will be called irrespective of the resolve or reject.

What is the Promise Chain?

The  promise.then() call always returns a promise. This promise will have the state as pending and result as undefined. It allows us to call the next .then method on the new promise.

When the first .then method returns a value, the next .then method can receive that. The second one can now pass to the third .then() and so on. This forms a chain of .then methods to pass the promises down. This phenomenon is called the Promise Chain.

image-105

Promise Chain

Here is an example:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
}).catch(error => {
    console.log('In the catch', error);
});

Here we first get a promise resolved and then extract the URL to reach the first Pokémon. We then return that value and it will be passed as a promise to the next .then() handler function. Hence the output,

https://pokeapi.co/api/v2/pokemon/1/

The .then method can return either:

  • A value (we have seen this already)
  • A brand new promise.

It can also throw an error.

Here is an example where we have created a promise chain with the .then methods which returns results and a new promise:

// Promise Chain with multiple then and catch
let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
}).then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
}).then(pokemon => {
    console.log(JSON.parse(pokemon));
}).catch(error => {
    console.log('In the catch', error);
});

In the first .then call we extract the URL and return it as a value. This URL will be passed to the second .then call where we are returning a new promise taking that URL as an argument.

This promise will be resolved and passed down to the chain where we get the information about the Pokémon. Here is the output:

image-159

Output of the promise chain call

In case there is an error or a promise rejection, the .catch method in the chain will be called.

A point to note: Calling .then multiple times doesn’t form a Promise chain. You may end up doing something like this only to introduce a bug in the code:

let promise = getPromise(ALL_POKEMONS_URL);

promise.then(result => {
    let onePokemon = JSON.parse(result).results[0].url;
    return onePokemon;
});
promise.then(onePokemonURL => {
    console.log(onePokemonURL);
    return getPromise(onePokemonURL);
});
promise.then(pokemon => {
    console.log(JSON.parse(pokemon));
});

We call the .then method three times on the same promise, but we don’t pass the promise down. This is different than the promise chain. In the above example, the output will be an error.

image-160

How to Handle Multiple Promises

Apart from the handler methods (.then, .catch, and .finally), there are six static methods available in the Promise API. The first four methods accept an array of promises and run them in parallel.

  1. Promise.all
  2. Promise.any
  3. Promise.allSettled
  4. Promise.race
  5. Promise.resolve
  6. Promise.reject

Let’s go through each one.

The Promise.all() method

Promise.all([promises]) accepts a collection (for example, an array) of promises as an argument and executes them in parallel.

This method waits for all the promises to resolve and returns the array of promise results. If any of the promises reject or execute to fail due to an error, all other promise results will be ignored.

Let’s create three promises to get information about three Pokémons.

const BULBASAUR_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/bulbasaur';
const RATICATE_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/raticate';
const KAKUNA_POKEMONS_URL = 'https://pokeapi.co/api/v2/pokemon/kakuna';


let promise_1 = getPromise(BULBASAUR_POKEMONS_URL);
let promise_2 = getPromise(RATICATE_POKEMONS_URL);
let promise_3 = getPromise(KAKUNA_POKEMONS_URL);

Use the Promise.all() method by passing an array of promises.

Promise.all([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('An Error Occured');
});

Output:

image-161

As you see in the output, the result of all the promises is returned. The time to execute all the promises is equal to the max time the promise takes to run.

The Promise.any() method

Promise.any([promises]) — Similar to the all() method, .any() also accepts an array of promises to execute them in parallel. This method doesn’t wait for all the promises to resolve. It is done when any one of the promises is settled.

 Promise.any([promise_1, promise_2, promise_3]).then(result => {
     console.log(JSON.parse(result));
 }).catch(error => {
     console.log('An Error Occured');
 });

The output would be the result of any of the resolved promises:

image-162

The Promise.allSettled() method

romise.allSettled([promises]) — This method waits for all promises to settle(resolve/reject) and returns their results as an array of objects. The results will contain a state (fulfilled/rejected) and value, if fulfilled. In case of rejected status, it will return a reason for the error.

Here is an example of all fulfilled promises:

Promise.allSettled([promise_1, promise_2, promise_3]).then(result => {
    console.log({result});
}).catch(error => {
    console.log('There is an Error!');
});

Output:

image-163

If any of the promises rejects, say, the promise_1,

let promise_1 = getPromise(POKEMONS_BAD_URL);

image-164

The Promise.race() method

Promise.race([promises]) – It waits for the first (quickest) promise to settle, and returns the result/error accordingly.

Promise.race([promise_1, promise_2, promise_3]).then(result => {
    console.log(JSON.parse(result));
}).catch(error => {
    console.log('An Error Occured');
});

Output the fastest promise that got resolved:

image-165

The Promise.resolve/reject methods

Promise.resolve(value) – It resolves a promise with the value passed to it. It is the same as the following:

let promise = new Promise(resolve => resolve(value));

Promise.reject(error) – It rejects a promise with the error passed to it. It is the same as the following:

let promise = new Promise((resolve, reject) => reject(error));

Can we rewrite the PizzaHub example with Promises?

Sure, let’s do it. Let us assume that the query method will return a promise. Here is an example query() method. In real life, this method may talk to a database and return results. In this case, it is very much hard-coded but serves the same purpose.

function query(endpoint) {
  if (endpoint === `/api/pizzahub/`) {
    return new Promise((resolve, reject) => {
      resolve({'shopId': '123'});
    })
  } else if (endpoint.indexOf('/api/pizzahub/pizza/') >=0) {
    return new Promise((resolve, reject) => {
      resolve({pizzas: [{'type': 'veg', 'name': 'margherita', 'id': '123'}]});
    })
  } else if (endpoint.indexOf('/api/pizzahub/beverages') >=0) {
    return new Promise((resolve, reject) => {
      resolve({id: '10', 'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  } else if (endpoint === `/api/order`) {
    return new Promise((resolve, reject) => {
      resolve({'type': 'veg', 'name': 'margherita', 'beverage': 'coke'});
    })
  }
}

Next is the refactoring of our callback hell. To do that, first, we will create a few logical functions:

// Returns a shop id
let getShopId = result => result.shopId;

// Returns a promise with pizza list for a shop
let getPizzaList = shopId => {
  const url = `/api/pizzahub/pizza/${shopId}`;
  return query(url);
}

// Returns a promise with pizza that matches the customer request
let getMyPizza = (result, type, name) => {
  let pizzas = result.pizzas;
  let myPizza = pizzas.find((pizza) => {
    return (pizza.type===type && pizza.name===name);
  });
  const url = `/api/pizzahub/beverages/${myPizza.id}`;
  return query(url);
}

// Returns a promise after Placing the order
let performOrder = result => {
  let beverage = result.id;
   return query(`/api/order`, {'type': result.type, 'name': result.name, 'beverage': result.beverage});
}

// Confirm the order
let confirmOrder = result => {
    console.log(`Your order of ${result.type} ${result.name} with ${result.beverage} has been placed!`);
}

Use these functions to create the required promises. This is where you should compare with the callback hell example. This is so nice and elegant.

function orderPizza(type, name) {
  query(`/api/pizzahub/`)
  .then(result => getShopId(result))
  .then(shopId => getPizzaList(shopId))
  .then(result => getMyPizza(result, type, name))
  .then(result => performOrder(result))
  .then(result => confirmOrder(result))
  .catch(function(error){
    console.log(`Bad luck, No Pizza for you today!`);
  })
}

Finally, call the orderPizza() method by passing the pizza type and name, like this:

orderPizza('veg', 'margherita');

What’s next from here?

If you are here and have read through most of the lines above, congratulations! You should now have a better grip of JavaScript Promises. All the examples used in this article are in this GitHub repository.

Next, you should learn about the async function in JavaScript which simplifies things further. The concept of JavaScript promises is best learned by writing small examples and building on top of them.

Irrespective of the framework or library (Angular, React, Vue, and so on) we use, async operations are unavoidable. This means that we have to understand promises to make things work better.

Also, I’m sure you will find the usage of the fetch method much easier now:

fetch('/api/user.json')
.then(function(response) {
    return response.json();
})
.then(function(json) {
    console.log(json); // {"name": "tapas", "blog": "freeCodeCamp"}
});
  • The fetch method returns a promise. So we can call the .then handler method on it.
  • The rest is about the promise chain which we learned in this article.

Before we end…

Thank you for reading this far! Let’s connect. You can @ me on Twitter (@tapasadhikary) with comments.

You may also like these other articles:

  • JavaScript undefined and null: Let’s talk about it one last time!
  • JavaScript: Equality comparison with ==, === and Object.is
  • The JavaScript `this` Keyword + 5 Key Binding Rules Explained for JS Beginners
  • JavaScript TypeOf – How to Check the Type of a Variable or Object in JS

That’s all for now. See you again with my next article soon. Until then, please take good care of yourself.



Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

Look at your console

function getNetworkstuff(url) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            console.log('resolving', url);
            resolve(url);
        }, 5000);
    });
}
Promise.all([[getNetworkstuff('url1')],[getNetworkstuff('url2')]])
.then(function(result){
    console.log('It worked', result);
})
.catch(function(err){
    console.log('It failed', result);
});

Notice it outputs «It worked», 5 seconds BEFORE anything is resolved

Promise.all([getNetworkstuff('url1'), getNetworkstuff('url2')])
.then(function(result){
    console.log('It worked', result);
})
.catch(function(err){
    console.log('It failed', result);
});

Now compare without the array of array — notice the difference in what is logged next to It Worked in both cases

and finally, run this

function getNetworkstuff(url) {
    return new Promise(function(resolve, reject) {
        if(url == 'url1') {
            setTimeout(function() {
                console.log('resolving', url);
                resolve(url);
            }, 5000);
        }
        else {
            console.log('rejecting', url);
            reject(url);
        }
    });
}

Promise.all([getNetworkstuff('url1'), getNetworkstuff('url2')])
.then(function(result){
    console.log('It worked', result);
})
.catch(function(err){
    console.log('It failed', result);
});

Your followup question: how are they kicked of if not being recognized as promises

Can you see that the code below is of a similar pattern to what you are doing with your array of array of results of functions that may or may not return promises? Anyway, take away the promises and the then stuff … and you’ve got this

function fn(s) {
    return s.toUpperCase();
}
function fn2(arr) {
    console.log(arr); // [["A"], ["B"]]
}
fn2([[fn('a')],[fn('b')]]);

Понравилась статья? Поделить с друзьями:
  • Promag 300 ошибка s441
  • Prolific ошибка код 10
  • Project64 ошибка программы ядра
  • Progressnotificationcommon dll citrix ошибка при установке
  • Programat p95 ошибка 28