Семантическое ядро

Examples

Consider this async task…

When you run tasks in positive scenario there is no difference between Promise.all and multiple await. Both examples end with after 5 seconds.

When first task takes 10 seconds in positive scenario and seconds task takes 5 seconds in negative scenario there are differences in errors issued.

We should already notice here that we are doing something wrong when using multiple await in parallel. Of course to avoid errors we should handle it! Let’s try…

As you can see to successfully handle error we need to add just one catch to function and code with catch logic is in callback (async style). We do not need handle errors inside function because async function it does automatically — promise rejection of function causes rejection of function. To avoid callback we can use sync style (async/await + try/catch) but in this example it is not possible because we can not use in main thread — it can be used only in async function (it is logical because nobody wants to block main thread). To test if handling works in sync style we can call function from another async function or use IIFE (Immediately Invoked Function Expression): .

This is only one correct way how to run two or more async parallel tasks and handle errors. You should avoid examples below.

We can try to handle code above several ways…

… nothing got caught because it handles sync code but is async

… Wtf? We see firstly that error for task 2 was not handled and later that was caught. Misleading and still full of errors in console. Unusable this way.

… the same as above. User @Qwerty in his deleted answer asked about this strange behavior that seems to be catched but there are also unhandled errors. We catch error because run() is rejected on line with await keyword and can be catched using try/catch when calling run(). We also get unhandled error because we are calling async task function synchronously (without await keyword) and this task runs outside run() function and also fails outside. It is similar when we are not able to handle error by try/catch when calling some sync function which part of code runs in setTimeout… .

… «only» two errors (3rd one is missing) but nothing caught.

Example: loadScript

We’ve got the function for loading a script from the previous chapter.

Here’s the callback-based variant, just to remind us of it:

Let’s rewrite it using Promises.

The new function will not require a callback. Instead, it will create and return a Promise object that resolves when the loading is complete. The outer code can add handlers (subscribing functions) to it using :

Usage:

We can immediately see a few benefits over the callback-based pattern:

Promises Callbacks
Promises allow us to do things in the natural order. First, we run , and we write what to do with the result. We must have a function at our disposal when calling . In other words, we must know what to do with the result before is called.
We can call on a Promise as many times as we want. Each time, we’re adding a new “fan”, a new subscribing function, to the “subscription list”. More about this in the next chapter: Promises chaining. There can be only one callback.

So promises give us better code flow and flexibility. But there’s more. We’ll see that in the next chapters.

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

«Чейнинг» (chaining), то есть возможность строить асинхронные цепочки из промисов – пожалуй, основная причина, из-за которой существуют и активно используются промисы.

Например, мы хотим по очереди:

  1. Загрузить данные посетителя с сервера (асинхронно).
  2. Затем отправить запрос о нём на github (асинхронно).
  3. Когда это будет готово, вывести его github-аватар на экран (асинхронно).
  4. …И сделать код расширяемым, чтобы цепочку можно было легко продолжить.

Вот код для этого, использующий функцию , описанную выше:

Самое главное в этом коде – последовательность вызовов:

При чейнинге, то есть последовательных вызовах , в каждый следующий переходит результат от предыдущего. Вызовы оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.

Если очередной вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.

В коде выше:

  1. Функция в первом возвращает «обычное» значение . Это значит, что возвратит промис в состоянии «выполнен» с в качестве результата. Он станет аргументом в следующем .
  2. Функция во втором возвращает промис (результат нового вызова ). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий с его результатом.
  3. Третий ничего не возвращает.

Схематично его работу можно изобразить так:

Значком «песочные часы» помечены периоды ожидания, которых всего два: в исходном и в подвызове далее по цепочке.

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

То есть, логика довольно проста:

  • В каждом мы получаем текущий результат работы.
  • Можно его обработать синхронно и вернуть результат (например, применить ). Или же, если нужна асинхронная обработка – инициировать её и вернуть промис.

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

Это общее правило: если внутри стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.

В данном случае промис должен перейти в состояние «выполнен» после срабатывания .

Строку для этого нужно переписать так:

Теперь, если к цепочке добавить ещё , то он будет вызван после окончания .

Install

Results: promisebench doxbee

file time(ms) memory(MB)
promises-bluebird-generator.js 727 19.15
callbacks-baseline.js 793 24.24
promises-bluebird.js 999 26.96
promises-tildeio-rsvp.js 1440 44.14
promises-lvivski-davy.js 1659 54.88
promises-cujojs-when.js 1828 68.23
callbacks-caolan-async-waterfall.js 1877 45.27
promises-rkatic-p.js 1970 75.13
promises-calvinmetcalf-lie.js 1974 69.22
promises-kaerus-component-uP.js 2142 76.63
promises-dfilatov-vow.js 2154 92.34
promises-obvious-kew.js 2857 99.19
promises-ecmascript6-native.js 3512 91.90
promises-zolmeister-promiz.js 3514 100.57
promises-rubenverborgh-promiscuous.js 3954 105.65
promises-ondras-promise.js 4308 121.43
promises-then-promise.js 4670 103.77
promises-medikoo-deferred.js 6719 135.53
promises-kriskowal-q.js 24697 373.72

Results: promisebench doxbee-errors

file time(ms) memory(MB)
callbacks-baseline.js 926 23.45
promises-bluebird-generator.js 1114 19.43
promises-bluebird.js 1231 27.09
promises-lvivski-davy.js 1313 43.43
promises-tildeio-rsvp.js 1474 46.67
promises-kaerus-component-uP.js 1880 63.23
promises-cujojs-when.js 2012 62.51
callbacks-caolan-async-waterfall.js 2146 45.30
promises-rkatic-p.js 2240 76.90
promises-calvinmetcalf-lie.js 2359 83.89
promises-dfilatov-vow.js 2552 81.71
promises-obvious-kew.js 3137 112.25
promises-zolmeister-promiz.js 3555 105.64
promises-rubenverborgh-promiscuous.js 4189 109.71
promises-then-promise.js 4222 100.93
promises-kriskowal-q.js 22253 422.80
promises-ondras-promise.js OOM OOM
promises-medikoo-deferred.js OOM OOM

Results: promisebench parallel

file time(ms) memory(MB)
promises-bluebird.js 1559 98.38
promises-bluebird-generator.js 1742 99.85
promises-kaerus-component-uP.js 1922 96.89
promises-tildeio-rsvp.js 2048 104.05
promises-lvivski-davy.js 3125 122.02
promises-calvinmetcalf-lie.js 3401 181.55
callbacks-caolan-async-parallel.js 3425 111.21
callbacks-baseline.js 3694 25.37
promises-dfilatov-vow.js 3712 213.29
promises-cujojs-when.js 3809 176.97
promises-then-promise.js 4039 237.55
promises-rkatic-p.js 4138 237.52
promises-ondras-promise.js 4528 214.64
promises-ecmascript6-native.js 9461 298.12
promises-obvious-kew.js 9956 340.46
promises-medikoo-deferred.js 12342 355.68
promises-rubenverborgh-promiscuous.js 17416 474.20
promises-zolmeister-promiz.js 53127 602.03

Note: Notice the — in npm. But yea, only in parallel tests. Pretty good.

Because of that I made module that checks for native or as checks for native or

Перехват ошибок

Выше мы рассмотрели «идеальный случай» выполнения, когда ошибок нет.

А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?

Да мало ли, где ошибка…

Правило здесь очень простое.

При возникновении ошибки – она отправляется в ближайший обработчик .

Такой обработчик нужно поставить через второй аргумент или, что то же самое, через .

Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим в конец нашей цепочки:

В примере выше ошибка возникает в первом же , но с тем же успехом поймал бы ошибку во втором или в .

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

А что после ?

Обработчик получает ошибку и должен обработать её.

Есть два варианта развития событий:

  1. Если ошибка не критичная, то возвращает значение через , и управление переходит в ближайший .
  2. Если продолжить выполнение с такой ошибкой нельзя, то он делает , и тогда ошибка переходит в следующий ближайший .

Это также похоже на обычный – в блоке ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает . Существенное отличие – в том, что промисы асинхронные, поэтому при отсутствии внешнего ошибка не «вываливается» в консоль и не «убивает» скрипт.

Ведь возможно, что новый обработчик будет добавлен в цепочку позже.

Promise.allSettled

A recent addition

This is a recent addition to the language.
Old browsers may need polyfills.

rejects as a whole if any promise rejects. That’s good for “all or nothing” cases, when we need all results successful to proceed:

just waits for all promises to settle, regardless of the result. The resulting array has:

  • for successful responses,
  • for errors.

For example, we’d like to fetch the information about multiple users. Even if one request fails, we’re still interested in the others.

Let’s use :

The in the line above will be:

So for each promise we get its status and .

If the browser doesn’t support , it’s easy to polyfill:

In this code, takes input values, turns them into promises (just in case a non-promise was passed) with , and then adds handler to every one.

That handler turns a successful result into , and an error into . That’s exactly the format of .

Now we can use to get the results of all given promises, even if some of them reject.

Что такое Promise?

Promise – это специальный объект, который содержит своё состояние. Вначале («ожидание»), затем – одно из: («выполнено успешно») или («выполнено с ошибкой»).

На можно навешивать колбэки двух типов:

  • – срабатывают, когда в состоянии «выполнен успешно».
  • – срабатывают, когда в состоянии «выполнен с ошибкой».

Способ использования, в общих чертах, такой:

  1. Код, которому надо сделать что-то асинхронно, создаёт объект и возвращает его.
  2. Внешний код, получив , навешивает на него обработчики.
  3. По завершении процесса асинхронный код переводит в состояние (с результатом) или (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.

Синтаксис создания :

Универсальный метод для навешивания обработчиков:

  • – функция, которая будет вызвана с результатом при .
  • – функция, которая будет вызвана с ошибкой при .

С его помощью можно назначить как оба обработчика сразу, так и только один:

.catch

Для того, чтобы поставить обработчик только на ошибку, вместо можно написать – это то же самое.

Синхронный – то же самое, что

Если в функции промиса происходит синхронный (или иная ошибка), то вызывается :

Посмотрим, как это выглядит вместе, на простом примере.

Promise.resolve/reject

Methods and are rarely needed in modern code, because syntax (we’ll cover it a bit later) makes them somewhat obsolete.

We cover them here for completeness and for those who can’t use for some reason.

creates a resolved promise with the result .

Same as:

The method is used for compatibility, when a function is expected to return a promise.

For example, the function below fetches a URL and remembers (caches) its content. For future calls with the same URL it immediately gets the previous content from cache, but uses to make a promise of it, so the returned value is always a promise:

We can write , because the function is guaranteed to return a promise. We can always use after . That’s the purpose of in the line .

creates a rejected promise with .

Same as:

In practice, this method is almost never used.

Bigger example: fetch

In frontend programming promises are often used for network requests. So let’s see an extended example of that.

We’ll use the fetch method to load the information about the user from the remote server. It has a lot of optional parameters covered in separate chapters, but the basic syntax is quite simple:

This makes a network request to the and returns a promise. The promise resolves with a object when the remote server responds with headers, but before the full response is downloaded.

To read the full response, we should call the method : it returns a promise that resolves when the full text is downloaded from the remote server, with that text as a result.

The code below makes a request to and loads its text from the server:

The object returned from also includes the method that reads the remote data and parses it as JSON. In our case that’s even more convenient, so let’s switch to it.

We’ll also use arrow functions for brevity:

Now let’s do something with the loaded user.

For instance, we can make one more requests to GitHub, load the user profile and show the avatar:

The code works; see comments about the details. However, there’s a potential problem in it, a typical error for those who begin to use promises.

Look at the line : how can we do something after the avatar has finished showing and gets removed? For instance, we’d like to show a form for editing that user or something else. As of now, there’s no way.

To make the chain extendable, we need to return a promise that resolves when the avatar finishes showing.

Like this:

That is, the handler in line now returns , that becomes settled only after the call of in . The next in the chain will wait for that.

As a good practice, an asynchronous action should always return a promise. That makes it possible to plan actions after it; even if we don’t plan to extend the chain now, we may need it later.

Finally, we can split the code into reusable functions:

Promise.all

Let’s say we want many promises to execute in parallel and wait until all of them are ready.

For instance, download several URLs in parallel and process the content once they are all done.

That’s what is for.

The syntax is:

takes an array of promises (it technically can be any iterable, but is usually an array) and returns a new promise.

The new promise resolves when all listed promises are settled, and the array of their results becomes its result.

For instance, the below settles after 3 seconds, and then its result is an array :

Please note that the order of the resulting array members is the same as in its source promises. Even though the first promise takes the longest time to resolve, it’s still first in the array of results.

A common trick is to map an array of job data into an array of promises, and then wrap that into .

For instance, if we have an array of URLs, we can fetch them all like this:

A bigger example with fetching user information for an array of GitHub users by their names (we could fetch an array of goods by their ids, the logic is identical):

If any of the promises is rejected, the promise returned by immediately rejects with that error.

For instance:

Here the second promise rejects in two seconds. That leads to an immediate rejection of , so executes: the rejection error becomes the outcome of the entire .

In case of an error, other promises are ignored

If one promise rejects, immediately rejects, completely forgetting about the other ones in the list. Their results are ignored.

For example, if there are multiple calls, like in the example above, and one fails, the others will still continue to execute, but won’t watch them anymore. They will probably settle, but their results will be ignored.

does nothing to cancel them, as there’s no concept of “cancellation” in promises. In another chapter we’ll cover that can help with that, but it’s not a part of the Promise API.

allows non-promise “regular” values in

Normally, accepts an iterable (in most cases an array) of promises. But if any of those objects is not a promise, it’s passed to the resulting array “as is”.

For instance, here the results are :

So we are able to pass ready values to where convenient.

JavaScript синтаксис:

p.then(onFulfilled);
p.then(onFulfilled, onRejected);

onFulfilled - Function
onRejected - Function

После выполнения или отклонения обещания соответствующая функция обработчика (onFulfilled, или onRejected) будет вызвана асинхронно (запланирована в текущем цикле потока). Поведение функции обработчика соответствует определенному набору правил. Если функция обработчика:

  • возвращает значение, то обещание, возвращенное методом .then() разрешается с этим значением;
  • ничего не возвращает, то обещание, возвращенное методом .then() разрешается с неопределенным значением (undefined);
  • вызывает ошибку, то обещание, возвращенное методом .then() отклоняется с вызванной ошибкой в качестве его значения;
  • возвращает уже разрешенное обещание, то обещание, возвращенное методом .then() разрешается со значением этого обещания в качестве его значения;
  • возвращает уже отклоненное обещание, то обещание, возвращенное методом .then() отклоняется со значением этого обещания в качестве его значения;
  • возвращает еще один объект Promise находящийся в состоянии ожидания, то разрешение, или отклонение обещания, возвращенного методом .then() будет следовать за разрешением, или отклонением обещания, возвращенного обработчиком. Кроме того, значение обещания, возвращаемого методом .then() будет таким же, как значение обещания, возвращаемого обработчиком.

First difference — fail fast

I agree with @zzzzBov’s answer but «fail fast» advantage of Promise.all is not only the one difference. Some users in comments asks why to use Promise.all when it is only faster in negative scenario (when some task fails). And I ask why not? If I have two independent async parallel tasks and first one is resolved in very long time but second is rejected in very short time why to leave user wait for error mesage «very long time» instead of «very short time»? In real-life applications we must consider negative scenario. But OK — in this first difference you can decide which alternative to use Promise.all vs. multiple await.

Promise в Ajax-запросах

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

Как видно из примеров прошлых тем для создания ajax-запросов используются фактически повторяющиеся вызовы, отличающиеся лишь деталями — строкой запроса,
функциями обработки ответа. И вполне было бы не плохо создать для всех действий, связанных с асинхронным ajax-запросом, создать какую-то общую
абстракцию и затем использовать ее при следующих обращениях к серверу.

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

Инкапсулируем асинхронный запрос в объект Promise:

function get(url) {
  return new Promise(function(succeed, fail) {
    var request = new XMLHttpRequest();
    request.open("GET", url, true);
    request.addEventListener("load", function() {
      if (request.status 

Метод get получает в качестве параметра адрес ресурса сервера и возвращает объект Promise. Конструктор Promise в качестве параметра принимает
функцию обратного вызова, которая в свою очередь принимает два параметра — две функции: одна выполняется при успешной обработке запроса, а вторая —
при неудачной.

Допустим, на сервере будет размещен файл users.json со следующим содержимым:

Теперь вызовем метод get для осуществления запроса к серверу:

get("http://localhost:8080/users.json").then(function(text) {
  console.log(text);
}, function(error) {
  console.log("Error!!!");
  console.log(error);
});

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

get("http://localhost:8080/users.json").then(function(response) {
	console.log(response);
	return JSON.parse(response);
}).then(function(data) {
	console.log(data);
});

В данном случае функция в первом вызове метода then получает ответ сервера и возвращает распарсенные данные в виде массива с помощью функции .

Функция во втором вызове then получает эти распарсенные данные, то есть массив, в виде параметра (возвращаемое значение предудыщего then является параметром для
последующего then). Затем первый элемент массива выводится на консоль.

Для обработки ошибок мы мы можем использовать метод catch(), в который передается функция обработки ошибок:

get("http://localhost:8080/users.jsn").then(function(response) {
	console.log(response);
	return JSON.parse(response);
}).then(function(data) {
	console.log(data);
}).catch(function(error){
	console.log("Error!!!");
	console.log(error);
});

Подобным образом через Promise можно было бы отправлять данные на сервер:

function post(url, requestuestBody) {
  return new Promise(function(succeed, fail) {
    var request = new XMLHttpRequest();
    request.open("POST", url, true);
	request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    request.addEventListener("load", function() {
      if (request.status 




Назад

API

Promise(callback)

    var promise = new Promise(function (resolve, reject) {//resolve表示成功,reject表示失败
        //异步操作
        setTimeout(function () {
            resolve('foo');//1s后标识成功
        }, 1000);
    });

Promise.prototype.then(onFulfilled)

    new Promise(function (resolve, reject) {
        
        //异步操作
        setTimeout(function () {
            resolve('foo');//1s后标识成功
        }, 1000);
        
    }).then(function (data) {
        
        //Resolved
        console.log(data);//1s后输出foo
        
        //传递结果到下一个链
        return 'bar';
        
    }, function () {
        
        //Rejected
        
    }).then(function (data) {
        
        console.log(data);//1s输出foo后,输出bar
        
    })

中的的回调函数可以重写Promise链:

    new Promise(function (resolve, reject) {
        //异步操作
        setTimeout(function () {
            resolve('foo');//1s后标识成功
        }, 1000);
        
    }).then(function (data) {
        //Resolved
        
        //重写Promise链
        return new Promise(function (resolve, reject) {
            
            setTimeout(function () {
                reject('bar');
            }, 1000);
            
        });
        
    }).then(function () {
        //Resolved
    }, function (data) {
        //Rejected
        console.log(data);//2s后,输出bar,
        
    })

Promise.prototype.catch(onRejected)

    new Promise(function (resolve, reject) {
        //异步操作
        setTimeout(function () {
            resolve('foo');//1s后标识成功
        }, 1000);
        
    }).then(function (data) {
        //Resolved
        throw Error('bar');
        
    }).catch(function (e) {
        //catch接住异常
        console.log(e);//1s后输出Eoor:bar
        
    });

具有冒泡性质,它的Error可以冒泡:

    var noop = function () { };
    new Promise(function (resolve, reject) {
        //异步操作
        setTimeout(function () {
            resolve('foo');//1s后标识成功
        }, 1000);

    }).then(function (data) {
        //Resolved
        throw Error('bar');

    }).then(noop)//多个Promise链
      .then(noop)
      .then(noop)
      .then(noop)
      .catch(function (e) {
          //catch接住异常
          console.log(e);//1s后输出Eoor:bar
      });

当Promise中的回调函数抛出异常之后(),需要使用进行捕获,否则catch会冒泡到全局环境下:

    new Promise(function () {
        //window Error => Uncaught (in promise) Error: ex => 触发未捕获异常
        throw new Error('ex');
    });

上面的代码会抛出全局异常,而需要使用进行捕获,的行为和一致,所以也可以使用进行捕获,下面两段代码是相同的的意义(捕获异常):

    new Promise(function () {
        throw new Error('ex');

    }).catch(function (e) {
        //Reject
        console.log(e);//捕获异常 输出Error: ex

    });


    //等同于上面的代码
    new Promise(function () {
        throw new Error('ex');

    }).then(function () {
        //Resolve
    }, function (e) {
        //Reject
        console.log(e);//捕获异常 输出Error: ex

    });

Promise.all(promises)

    var promise1 = new Promise(function (resolve) {
        //异步任务
        setTimeout(function () {
            resolve('foo');
        }, 100);

    }), promise2 = new Promise(function (resolve) {
            setTimeout(function () {
                resolve('bar');
            }, 200);
    });

    Promise.all().then(function (datas) {
        //Resolve
        console.log(datas);//约200ms以后,输出:

    }, function () {
        //Rejected
    });

Promise.race(promises)

    var promise1 = new Promise(function (resolve) {
        //异步任务
        setTimeout(function () {
            resolve('foo');
        }, 100);

    }), promise2 = new Promise(function (resolve,reject) {

        setTimeout(function () {
            reject('bar');//失败
        }, 200);
    });

    Promise.all().then(function (datas) {
        //Resolve
        console.log(datas);//约100ms以后,输出:'foo'

    }, function () {
        //Rejected
    });

Promise.resolve(value)

    //demo1
    var promise1 = new Promise(function (resolve) {
        //异步任务
        setTimeout(function () {
            resolve('foo');
        }, 100);

    });

    Promise.resolve(promise1).then(function (datas) {
        //Resolve
        console.log(datas);//约100ms以后,输出:'foo'

    }, function () {
        //Rejected
    });


    //demo2
    Promise.resolve(true).then(function (data) {

        //Resolve
        console.log(data);//直接输出true
    });

Promise.reject(value)

    Promise.reject(false).then(function (data) {
        //Resolve => 不会执行
        console.log(data);

    }, function (value) {
        //Rejected
        console.log(value);//直接输出false

    });

##兼容性

当前仅兼容到支持ECMAScript 5的(现代)浏览器,有需求可以兼容低版本浏览器。

Example: loadScript

Let’s use this feature with the promisified , defined in the , to load scripts one by one, in sequence:

This code can be made bit shorter with arrow functions:

Here each call returns a promise, and the next runs when it resolves. Then it initiates the loading of the next script. So scripts are loaded one after another.

We can add more asynchronous actions to the chain. Please note that the code is still “flat” — it grows down, not to the right. There are no signs of the “pyramid of doom”.

Technically, we could add directly to each , like this:

This code does the same: loads 3 scripts in sequence. But it “grows to the right”. So we have the same problem as with callbacks.

People who start to use promises sometimes don’t know about chaining, so they write it this way. Generally, chaining is preferred.

Sometimes it’s ok to write directly, because the nested function has access to the outer scope. In the example above the most nested callback has access to all variables , , . But that’s an exception rather than a rule.

Thenables

To be precise, a handler may return not exactly a promise, but a so-called “thenable” object – an arbitrary object that has a method . It will be treated the same way as a promise.

The idea is that 3rd-party libraries may implement “promise-compatible” objects of their own. They can have an extended set of methods, but also be compatible with native promises, because they implement .

Here’s an example of a thenable object:

JavaScript checks the object returned by the handler in line : if it has a callable method named , then it calls that method providing native functions , as arguments (similar to an executor) and waits until one of them is called. In the example above is called after 1 second . Then the result is passed further down the chain.

This feature allows us to integrate custom objects with promise chains without having to inherit from .

Promise.allSettled

Новая возможность

Эта возможность была добавлена в язык недавно.
В старых браузерах может понадобиться полифил.

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

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

  • для успешных завершений,
  • для ошибок.

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

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

Массив в строке будет таким:

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

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

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

Этот обработчик превращает успешный результат в , а ошибку в . Это как раз и есть формат результатов .

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

Пример использования

Базовое использования метода

const promise = new Promise(function(resolve, reject) {
    setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 1000, "promise2"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 1 секунду
});

const promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды
});

Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

В этом примере мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первом случае, через одну секунду во втором и через пол секунды в третьем.

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

С использованием метода then() мы добавили обработчик, вызываемый когда объект Promise имеет состояние fulfilled (успешное выполнение), и выводим в консоль полученное значение (массив полученных значений из всех обещаний).

Далее мы с Вами рассмотрим пример в котором увидим, что произойдет, если одно из обещаний будет отклонено:

const promise = new Promise(function(resolve, reject) {
    setTimeout(resolve, 2000, "promise1"); // изменяем состояние объекта на fulfilled (успешное выполнение) через 2 секунды
});

const promise2 = new Promise(function(resolve, reject) {
    setTimeout(reject, 1000, new Error("Обещание отклонено")); // изменяем состояние объекта на rejected (выполнение отклонено) через 1 секунду
});

const promise3 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 500, "promise3"); // изменяем состояние объекта на fulfilled (успешное выполнение) через пол секунды
});

Promise.all([promise, promise2, promise3]) // ожидаем выполнение всех переданных в массиве обещаний / или отклонения какого-то обещания
                .then(val => console.log(val), // обработчик для успешного выполнения
                           err => console.log(err.message)); // обработчик для случая, когда выполнение отклонено

// Обещание отклонено

По аналогии с предыдущим примером мы инициализировали три переменные, которые содержат объект Promise, который изменяет состояние объекта на fulfilled (успешное выполнение) через 2 секунды в первой и через пол секунды в третьей переменной

Обратите внимание, что с помощью метода reject() мы изменяем значение объекта Promise на rejected (выполнение отклонено) через 1 секунду во второй переменной

С использованием метода .all() объекта Promise мы принимаем массив, который содержит ссылку на три обещания, результат выполнения этого метода зависит от того с каким результатом завершится выполнение обещаний, в нашем случае он возвращает объект Promise, который имеет состояние rejected (выполнение отклонено), это связано с тем, что один из переданных объектов изменил своё состояние на rejected (выполнение отклонено).

С использованием метода then() мы добавили обработчики, вызываемые когда объект Promise имеет состояние fulfilled (успешное выполнение), или rejected (выполнение отклонено). В нашем случае срабатывает обработчик для отклоненного выполнения, и выводит информацию об ошибке в консоль.

Нюансы использования метода

Promise.all()
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

Promise.all()
                .then(val => console.log(val)); // обработчик для успешного выполнения

// 

Promise.all([])
                .then(val => console.log(val)); // обработчик для успешного выполнения

// []            

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

JavaScript Promise

Пример: loadScript

У нас есть функция для загрузки скрипта из предыдущей главы.

Давайте вспомним, как выглядел вариант с колбэками:

Теперь перепишем её, используя .

Новой функции не будет нужен аргумент . Вместо этого она будет создавать и возвращать объект , который будет переходить в состояние «успешно завершён», когда загрузка закончится. Внешний код может добавлять обработчики («подписчиков»), используя :

Применение:

Сразу заметно несколько преимуществ перед подходом с использованием колбэков:

Промисы Колбэки
Промисы позволяют делать вещи в естественном порядке. Сперва мы запускаем , и затем () мы пишем, что делать с результатом. У нас должна быть функция на момент вызова . Другими словами, нам нужно знать что делать с результатом до того, как вызовется .
Мы можем вызывать у столько раз, сколько захотим. Каждый раз мы добавляем нового «фаната», новую функцию-подписчика в «список подписок». Больше об этом в следующей главе: Цепочка промисов. Колбэк может быть только один.

Таким образом, промисы позволяют улучшить порядок кода и дают нам гибкость. Но это далеко не всё. Мы узнаем ещё много полезного в последующих главах.

Ссылка на основную публикацию