Статьи

Выполнение обещаний с помощью JavaScript

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

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

Вот как выглядит обещание.

1
var myPromise = new Promise(function (resolve, reject) { // Task to carry out goes here. });

Здесь вы можете видеть, что когда мы создаем обещание, мы даем ему один аргумент, который представляет собой функцию, содержащую код, который мы хотели бы выполнить в какой-то момент в будущем. Возможно, вы также заметили два аргумента в функции, переданные обещанию, resolve и reject . Это также функции, и мы можем сказать Обещанию, выполнил ли он то, что обещал. Вот как бы вы их использовали:

1
2
3
4
5
6
7
var myPromise = new Promise(function (resolve, reject) {
    if (true) {
        resolve(‘Hello Tuts+ fans!’);
    } else {
        reject(‘Aww, didn\’t work.’);
    }
});

Это обещание, очевидно, всегда будет выполнено, так как утверждение if всегда будет верным. Это просто для целей обучения — мы сделаем что-то более реалистичное позже — но представьте себе, заменяя true фрагментом кода, в котором вы не были уверены на 100%, что это сработает.

Теперь, когда мы создали обещание, как мы его используем? Ну, нам нужно сказать, что это за функции resolve и reject . Мы делаем это с помощью метода then обещания.

1
2
3
4
5
6
7
myPromise.then(function (result) {
    // Resolve callback.
    console.log(result);
}, function (result) {
    // Reject callback.
    console.error(result);
});

Поскольку наш оператор if всегда проходит свою true проверку, приведенный выше код всегда будет регистрировать «Hello Tuts + fan!» на консоль. Это также сделает это немедленно. Это связано с тем, что код внутри конструктора Promise является синхронным, то есть он не ожидает выполнения какой-либо операции. Он имеет всю информацию, необходимую для продолжения, и делает это как можно скорее.

Однако обещания действительно сияют, когда речь идет об асинхронных задачах — задачах, в которых вы не знаете, когда именно обещание будет выполнено. Реальным примером асинхронной задачи является получение ресурса, например, файла JSON, через AJAX. Мы не знаем, сколько времени потребуется серверу, чтобы ответить, и он может даже потерпеть неудачу. Давайте добавим немного AJAX в наш код обещания.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var myPromise = new Promise(function (resolve, reject) {
    // Standard AJAX request setup and load.
    var request = new XMLHttpRequest();
     
    // Request a user’s comment from our fake blog.
    request.open(‘GET’, ‘http://jsonplaceholder.typicode.com/posts/1’);
 
    // Set function to call when resource is loaded.
    request.onload = function () {
        if (request.status === 200) {
            resolve(request.response);
        } else {
            reject(‘Page loaded, but status not OK.’);
        }
    };
 
    // Set function to call when loading fails.
    request.onerror = function () {
        reject(‘Aww, didn\’t work at all.’);
    }
 
    request.send();
});

Код здесь — это просто стандартный JavaScript для выполнения AJAX-запроса. Мы запрашиваем ресурс, в данном случае файл JSON по указанному URL-адресу, и ожидаем ответа. Мы никогда не узнаем точно когда. И мы, очевидно, не хотим останавливать выполнение сценария out, чтобы ждать его, так что же нам делать?

Что ж, к счастью, мы поместили этот код в обещание. Размещая это здесь, мы в основном говорим: «Эй, кусок кода, мне нужно идти прямо сейчас, но я позвоню тебе позже и скажу, когда выполнять. Обещай, что ты сделаешь это и скажешь мне когда вы закончите?» И код скажет: «Да, конечно. Я обещаю».

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

Используя тот же код из нашего основного примера, мы можем видеть, как теперь будет работать наш AJAX-запрос внутри обещания.

01
02
03
04
05
06
07
08
09
10
// Tell our promise to execute its code
// and tell us when it’s done.
myPromise.then(function (result) {
    // Prints received JSON to the console.
    console.log(result);
}, function (result) {
    // Prints «Aww didn’t work» or
    // «Page loaded, but status not OK.»
    console.error(result);
});

Я знал, что мы можем доверять тебе, myPromise .

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
myPromise
   .then(function (result) {
       // Once we receive JSON,
       // turn it into a JSON object and return.
       return JSON.parse(result);
   })
   .then(function (parsedJSON) {
       // Once json has been parsed,
       // get the email address and make it lowercase.
       return parsedJSON.email.toLowerCase();
   })
   .then(function (emailAddress) {
       // Once text has been made lowercase,
       // print it to the console.
       console.log(emailAddress);
   }, function (err) {
       // Something in the above chain went wrong?
       // Print reject output.
       console.error(err);
   });

Здесь вы можете видеть, что, хотя наш первоначальный вызов был асинхронным, синхронные вызовы также можно объединять в цепочку. Код в каждой функции resolve внутри функции then будет вызываться при каждом возвращении. Вы также заметите, что здесь указана только одна функция ошибок для всей цепочки. Поместив это в конец цепочки как функцию reject в последнем, любое обещание в цепочке, которое вызывает reject , вызовет это.

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

1
2
3
4
5
6
7
8
9
var sendEmail = function (emailAddress) {
    return new Promise(function (resolve, reject) {
        // Pretend to send an email
        // or do something else asynchronous
        setTimeout(function () {
            resolve(‘Email sent to ‘ + emailAddress);
        }, 3000);
    });
};

Мы используем вызов setTimeout чтобы просто подделать задачу, выполнение которой занимает несколько секунд асинхронно.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
myPromise
   .then(function (result) {
       return JSON.parse(result);
   })
   .then(function (parsedJSON) {
       return parsedJSON.email.toLowerCase();
   })
   .then(function (emailAddress) {
       return sendEmail(emailAddress)
   })
   .then(function (result) {
       // Outputs «Email sent to [email protected]»
       console.log(result);
   }, function (err) {
       console.error(err);
   });

Давайте рассмотрим этот поток, чтобы просто подвести итог тому, что происходит. Наше оригинальное обещание myPromise запрашивает кусок JSON. Когда этот JSON получен (мы не знаем, когда), мы превращаем JSON в объект JavaScript и возвращаем это значение.

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

Я надеюсь, что это было полезное введение в Promises и дало вам вескую причину начать использовать их в ваших проектах JavaScript. Если вы хотите узнать больше об обещаниях более подробно, ознакомьтесь с отличной статьей Jake Archibald HTML5 Rocks на эту тему.

Изучите JavaScript: полное руководство

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