ECMAScript 6 Promises

  • Tutorial
На Хабре уже встречались статьи о замечательной технологии Promises, которая в будущем станет частью стандарта ECMAScript 6, однако, в этих статьях не было подробного описания, почему же они так полезны и в чем таки их преимущества. Дабы заполнить этот пробел, я решил написать эту статью.

Внимание! Данная статья — не исчерпывающее руководство. Это скорее пиар хорошей и полезной технологии, замануха, показывающая позитивные стороны. Статья является компиляцией нескольких чужих статей, ссылки внизу.

Итак, что же такое Promise?


  • Promise это объект, используемый как заглушка для результата некоего отложенного (и возможно асинхронного) вычисления
  • Способ писать последовательно/параллельно выполняемый асинхронный код как синхронный
  • Часть стандарта ECMAScript 6


Важные основы


  • Конструктор Promise — это имплементация паттерна Revealing Constructor Pattern
  • «Статичные» методы
  • Promise.resolve(val) and Promise.reject(val)
  • Promise.all and Promise.race
  • Концепция “Thenable” object
  • В двух словах — это объект, обладающий методами .then() and .catch()


Состояния Promise


  1. Fulfilled — вычисление было успешным
  2. Rejected — ошибка при вычислении (любая)
  3. Pending — вычисление еще не завершилось (не fulfilled и не rejected)
  4. Settled — вычисление завершилось (не важно как)


Примеры


Итак, возьмем кусок синхронного кода
function foo() {
	var a = ‘a’;
	a = a + ‘b’;
	a = a + ‘c’;
	return a;
}

И сделаем так, чтобы снаружи он выглядел как Promise:
function foo() {
	var a = ‘a’;
	a = a + ‘b’;
	a = a + ‘c’;
	return Promise.resolve(a);
}

Следующий шаг — раздели каждый этап вычисления:
function foo() {
	return Promise.resolve(‘a’)
		.then(function(a){ return a + ‘b’; })
		.then(function(a){ return a + ‘c’; });
}

Теперь каждый шаг можно сделать асинхронным, причем все выполнение будет по-прежнему последовательным.
Пойдем дальше, заменим один из шагов на «как бы асинхронную функцию, возвращающую Promise»:
function getB(a){ return Promise.resolve(a + ‘b’); }

function foo() {
	return Promise.resolve(‘a’)
		.then(function(a){ return getB(a); })
		.then(function(a){ return a + ‘c’; });
}

Встроенная функциональность допускает возврат в then(cb()) либо ошибки (throw new Error()) либо значения (return a+'c';) либо следующего Promise.

Параллелизм


Представим, что сперва надо выполнить асинхронное действие 1, затем параллельно 2 и 3, и затем 4.
asyncAction1()
	.then(function(res1){
		return Promise.all([async2(res1), async3(res1)]);
	})
	.then(function(arr){ // an array of values
		var res2 = arr[0], res3 = arr[1];
		return asyncAction4(res2, res3);
	})
	.then(…);


Обработка ошибок


Самая замечательная вещь в Promises — это обработка ошибок. Не важно, на каком этапе и в какой глубине вложенности произошла ошибка, будь то reject или просто брошенное исключение, все это можно поймать и обработать, либо же прокинуть дальше.
asyncAction()
	.catch(function(rejection){
		// пытаемся понять, можно ли как-то обработать ошибку 
		if (rejection.code == ‘foo’) return ‘foo’;
		// никак нельзя, прокидываем ошибку дальше
		throw rejection;
	})
	.then(…) 
	.then(…)
	.catch(…);

Здесь нужно сделать замечание, что если, положим
var p1 = new Promise(...),
    p2 = new Promise(...)
    p3 = Promise.all([p1, p2]);

p3.then(...).catch(...);

Catch будет ловить все, что пошло не так (причем не важно, что и как именно) в p1, p2, p3 и любых вложенных вызовах, что дико удобно. Обратная сторона — если catch() нет, то ошибка будет тихо проглочена. Однако библиотеки типа Q, как правило, имеют возможность задать обработчик непойманных ошибок, где их можно вывести в консоль или сделать что-то еще.

Слово об анти-паттернах


function anAsyncCall() {
	var promise = doSomethingAsync();
	promise.then(function(){
		somethingComplicated();
	});
	return promise;
}

Иии легким движением руки мы потеряли второй Promise. Дело в том, что каждый вызов .then() или .catch() создает новый Promise, поэтому если создали новый, а вернули старый, то новый повиснет где-то в воздухе и никто не узнает, каков результат вычисления. Как бороться — просто вернуть новый Promise:
return promise.then(...);


Полезные ништяки



Задержка выполнения


function delay(ms){
	return new Promise(function(resolve){
		setTimeout(resolve, ms);
	}
};

Пример использования
delay(5000).then(…);


Простейший таймаут


Поскольку Promise может быть settled только один раз (остальное игнорируется), то можно написать что-то вроде
function timeout(promise, ms) {
	return new Promise(function (resolve, reject) {
		promise.then(resolve);
		setTimeout(function () {
			reject(new Error('Timeout’));
		}, ms);
	});
}

timeout(asyncAction(), 5000).then(…).catch(…);

Кто первый встал — того и тапки.

Немного улучшенный таймаут


Чуть более очевидный пример таймаута через «статичную» функцию Promise.race().
Promise.race([
	asynchronousAction(),
	delay(5000).then(function () {
		throw new Error('Timed out');
	})
])
.then(function (text) { ... })
.catch(function (reason) { ... });


Важное замечание об асинхронности


  1. Библиотека контролирует процесс выполнения, соответственно она заведует, как доставляется результат — синхронно или асинхронно
  2. В то же время, спецификация Promises/A+ требует, чтобы всегда использовался последний режим — асинхронный
  3. Таким образом, мы всегда можем полагаться на немедленное выполнение кода promise.then().catch() и т.д., и не заботиться, что какой-то их коллбеков съест все процессорное время (с оговорками, само собой)


Почему Promises лучше callbacks


  • Они часть стандарта — умные люди думали и разрабатывали всякое для нашего удобства
  • «Практически» не сказываются на производительности (http://thanpol.as/javascript/promises-a-performance-hits-you-should-be-aware-of)
  • Попробуйте разрулить нетривиальный поток выполнения на callbacks, желательно с обработкой ошибок, если вы не напишете свою имплементацию Promises, код, скорее всего, будет невозможно читать и понимать
  • Все Promises/A+ совместимые библиотеки могут принимать объекты друг друга (Angular прекрасно работает с объектами Q/Native Promise/RSVP и т.д.)
  • А еще Promise — лучше, чем Deferred, потому что последний — это две концепции в одной, что безусловно плохо, а кроме этого, паттерн Revealing Constructor почти что гарантирует, что Promise будел settled только в рамках этого конструктора (ну или вы сам себе злобный Буратино)


Недостатки


  1. Не подходит для повторяющихся событий (правда, Promises не для того и писались)
  2. Не подходит для streams (аналогично)
  3. Текущая реализация в браузерах не позволяет следит за progress (собираются тоже включить в стандарт)


Замечание о jQuery


Реализация «Thennable» в jQuery немного отличается от стандартной — в стандарте аргумент в callback ровно один, в jQuery кол-во аргументов больше, что не мешает использовать объект, полученный из jQuery в нативной реализации. Как правило, больше, чем первый аргумент, ничего от jQuery и не нужно. Плюс существует конструкция:
var jsPromise = Promise.resolve($.ajax('/whatever.json'));


Ссылки для самостоятельного чтения


Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 47

    –1
    В недостатки нативных ES6 promises я бы еще добавил отсутствие метода finally() и не очень интуитивно понятный способ создания зарезолвленых промисов (Promise.all([])). С другой стороны, выкидывать лишние библиотеки из проектов всегда приятно :)
      +1
      Можете пояснить, про Promise.all — для создания заресолвленного Promise нужно просто вызвать Promise.resolve?
        0
        Да, согласен, с Promise.all я затупил
        0
        не знаю, как в стандарте, а в jQuery можно вроде $.then(true).
          0
          В стандарте можно Promise.resolve(true)
          0
          .finally() можно заменить конструкцией .then().catch().then() и не выбрасывать exception заново в catch(), тогда будет считаться, что блок отлова ошибок ошибку поймал, обработал и вернул выполнение в нормальное русло.

          По стандарту каждый блок .then() или .catch() выдает наружу новый promose, соотв. если в .catch() не перепрокинуть ошибку

          .catch(function(e){
            // do something
            throw e;
          })
          


          и вернуть некий результат или другой primise, то будет считаться, что нормальный ход выполнения восстановлен.
          +3
          Эх, если уж так хотите es6 — берите co+генераторы, они куда лучше. Откуда эта истерия про то что промисы — панацея?

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

          getTokenInfoFor(token).then(function(tokenInfo){
              getUserInfo(tokenInfo.email).then(function(userInfo){
                 if (userInfo.tokenRevokeTime > tokenInfo.issueTime) 
                     return Q.resolve(userInfo);
                 else 
                     return Q.reject(errors.tokenRevoked);
              }); 
          });
          


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

          на генераторах же будет как-то так
          co(function*(){
              var tokenInfo = yield getTokenInfoFor(token);
              var userInfo = yield getUserInfo(tokenInfo.email);
              if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
                  throw e;
              return userInfo;
          })(function(err, res){
              ...
          });
          


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

          И как вы понятную и человекочитаемую рекурсию на промисах сделаете?
          У меня регулярно бывают задачи по выборке из базы данных с рекурсивными условиями (сейчас, например — взять первые n элементов, отсортированных по приоритету, с условием, что берутся все элементы с заданным приоритетом, и общая выборка не меньше определенного числа), их на промисах охренеешь делать.

          В общем промисы — да, это удобно. Но перегрето. Полгода-год назад вообще какая-то истерия про них была, непонятная для меня.
            +4
            Как программист C#, я испытываю боль от одного взгляда на промисы. В дотнете промисам и сопрограммам соответствуют таски (Task) и асинхронные методы (async/await). Однажды попробовав второе, возвращаться к первому нет никакого желания. Ну да, оба лучше, чем колбэки, но всё равно ведь ужас, если вызовы не в цепочку. Читать невозможно.
              +1
              Для node.js есть реализация async/await: github.com/yortus/asyncawait. Однажды попробовав, промисы использоваться больше не захочется.
                0
                в traceur тоже есть вроде как, экспериментальная. но мне что-то Co+генераторы удобнее будет
                  –2
                  Тащить async/await в js — это уже перебор, пусть этот ужас останется в .NET. В js есть множество своих методов и паттернов работы с callback'ами. По мне так async выполняет туже работу, что и asyncawait, только в более близком к js стилю.
                    +1
                    Что такого ужасного в async/await?
                    Синтаксис 1 в 1 такой же, как и с генераторами (которых все уже ждут-не дождутся), только возможностей больше, да и доступы уже сейчас.

                    Пример подсчета файлов в обоих случаях.
                    // все нужные require
                    
                    // co + генераторы
                    var countFiles = co(function* (dir) {
                      var files = yield fs.readdirSync(dir);
                      var paths = _.map(files, function (file) { return path.join(dir, file); });
                      var stats = yield _.map(paths, function (path) { return fs.statAsync(path); });
                      return _.filter(stats, function (stat) { return stat.isFile(); }).length;
                    });
                    
                    // async/await
                    var countFiles = async.cps (function (dir) {
                      var files = await (fs.readdirSync(dir));
                      var paths = _.map(files, function (file) { return path.join(dir, file); });
                      var stats = await (_.map(paths, function (path) { return fs.statAsync(path); }));
                      return _.filter(stats, function (stat) { return stat.isFile(); }).length;
                    });
                    


                    Как можно радоваться от промисов при виде этого стройного кода я не понимаю.
                    Причем даже в сложных ситуациях это все не разваливается а продолжает выглядеть понятно, как синхронный код. О промисах и callback'ах с async такого сказать нельзя.
                      +3
                      Как можно радоваться от промисов при виде этого стройного кода я не понимаю.

                      С точки зрения будущего стандарта языка (с которым лучше ознакомиться), да и текущего, идеологически более верно что-то в таком стиле:

                      var files = await promiseFs.readdir(dir)
                        , paths = files.map((file)=> path.join(dir, file))
                        , stats = await Promise.all(paths.map((path)=> promiseFs.stat(path)))
                        , countFiles = stats.filter((stat)=> stat.isFile()).length;
                      
                        0
                        Либо я так долго работал в таком стиле, либо все мои проекты и коллеги с кем я работаю придерживаются того, что js — это callbacks, async, promises, service bus и т.д. Но когда я вижу рекомендации писать js код в sync виде — выглядит это жутко.
                        0
                        Каких возможностей больше? На первый взгляд, при наличие генераторов async-await — просто лишняя сущность. Фактически одно и то же, только ключевые слова разные.
                          0
                          Например, несколько интерфейсов у async функции: она может возвращать thunk, promise или принимать обычный callback в традиционном стиле; await тоже принимает все варианты. Это, конечно, просто возможности библиотеки и аналог можно реализовать и в co, но на данный момент получается, что asyncawait лучше будет работать с существующим кодом.

                          Есть еще небольшая фича, которую, как мне кажется, с помощью генераторов сделать не получится: внутри async-функции любые другие async-функции можно вызывать без await. Т.е. для своего кода можно достичь результата, где весь код выглядит синхронным и await даже не упоминается, а если сделать аналог require('bluebird').promisifyAll(require('fs')) для async функций (т.е. async.asyncifyAll()), то и вообще весь код станет визуально синхронным.

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

                            Второе — это вылитые волокна бы получились :-)
                      0
                      Там промисы внутри

                      Basic Example

                      var async = require('asyncawait/async');
                      var await = require('asyncawait/await');
                      var Promise = require('bluebird');
                      var fs = Promise.promisifyAll(require('fs')); // adds Async() versions that return promises
                      var path = require('path');
                      var _ = require('lodash');

                      /** Returns the number of files in the given directory. */
                      var countFiles = async (function (dir) {
                      var files = await (fs.readdirAsync(dir));
                      var paths = _.map(files, function (file) { return path.join(dir, file); });
                      var stats = await (_.map(paths, function (path) { return fs.statAsync(path); })); // parallel!
                      return _.filter(stats, function (stat) { return stat.isFile(); }).length;
                      });
                    +2
                    Генераторы это замечательно, но не панацея. Генераторы — возможность асинхронного ожидания результата функции, когда промисы — универсальный интерфейс асинхронных функций и способ обработки ошибок в них. async / await, что грозятся попасть в ES7, базируется как раз на связке генераторов и промисов.

                    Promise поддерживается всеми современными браузерами, для старых достаточно простого полифила, когда генераторы требуют компиляции в лютый трэш, при взгляде на который седеешь.
                      –2
                      ну, так может не стоит просто смотреть в результат?) главное, что работает
                        +1
                        А дебажить потом как, если не смотреть в результат?
                          +1
                          эмгх, sourcemaps же.
                            0
                            Я недавно делал проект на TypeScript, где sourcemap тоже есть, однако нормально дебажить все равно неудобно, т.к. что-то где-то все равно не так названо может быть, либо не на ту строку брейкпоинт встает. Короче говоря, помогает, конечно, но с оговорками. Большими такими оговорками.
                          0
                          Не только это главное.
                        +1
                        Промисы хороши там, где идет цепочечная обработка данных

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

                        getTokenInfoFor(token).then(function(tokenInfo){
                            return getUserInfo(tokenInfo.email).then(function(userInfo){ // подменяем результат выполнения первого промиса вторым
                               if (userInfo.tokenRevokeTime < tokenInfo.issueTime) throw errors.tokenRevoked; // выброс ошибки приведет к reject всех промисов ниже по цепи
                               return userInfo; // отдаем это как результат второго промиса
                            }); 
                        })
                        .then(function(userInfo){ ... })
                        .catch(function(rejection){ ... });
                        


                        Вы, фактически, применили антипаттерн из статьи, потеряв ваш промис. Если добавятся еще какие-то действия, то их можно подключать в цепочку хоть до посинения и без дикой вложенности.
                          0
                          Не вижу сильной разницы между моим и вашим кодом, вы просто заменили reject на throw, а resolve на return. Catch относительно дорогая операция, так что лучше ее избегать.

                          В реальном коде было четыре(!) уровня вложенности, и от них реально было никуда не деться. Я писал пример по памяти, так что…
                            0
                            Приглядитесь, в моем коде ошибки промисов не потеряются, а в Вашем — они никак не будут обработаны. А так разница мала, да. Насчет уровней вложенности, готов поспорить, что их можно было бы вывернуть из вложенности в обычную цепочку, если правильно модифицировать результат Promise'ов.
                              0
                              эмм. ну я же не весь код показывал все-таки.
                              Вы серьезно считаете, что ошибка авторизации по токену никак бы не обрабатывалась?
                          0
                          Реализовать волокна на генераторах, безусловно, можно, но это использование инструмента не по назначению (костыль со множеством ограничений). Если есть возможность лучше использовать нативные волокна (node-fibers, например). С ними асинхронность не просачивается за пределы асинхронной функции:
                          function getUserInfoByToken( token ) {
                              var tokenInfo = getTokenInfoFor(token);
                              var userInfo = getUserInfoByMail(tokenInfo.email);
                              if (userInfo.tokenRevokeTime > tokenInfo.issueTime)
                                  throw e;
                              return userInfo;
                          }
                          


                          Для себя я реализовал несколько хелперов:
                          $jin.async2sync — превращает асинхронную (в стиле nodejs) функцию в синхронную, которая останавливает текущее волокно до окончания своего исполнения
                          $jin.sync2async — наоборот, превращает синхронную в асинхронную (при необходимости заворачивает её в волокно)

                          Рекомендую почитать эту статью, где объясняется почему генераторы — это плохо, а волокна — хорошо: howtonode.org/generators-vs-fibers
                          Впрочем, генераторы, конечно, лучше чем обещания :-)
                            0
                            Можно попробовать так:

                            var tokenInfo = getTokenInfoFor(token),
                                userInfo = tokenInfo.then(function(tokenInfo){
                                    return getUserInfo(tokenInfo.email)
                                });
                            Q.all(tokenInfo, userInfo).spread(function(tokenInfo, userInfo){
                                if (userInfo.tokenRevokeTime > tokenInfo.issueTime) 
                                    return Q.resolve(userInfo);
                                else 
                                    return Q.reject(errors.tokenRevoked);
                            }); 
                            


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

                            Все верно, промисы — навороченные колбеки. Но, как выше писали, это не панацея, есть и другие подходы. Я о событиях.
                              0
                              События для другого. Promise одноразовый по определению, события — сущность многоразовая. Не стоит мешать две разные концепции, их следует использовать строго там, где это дает наибольший профит.
                                –2
                                Какая принципиальная разница, сколько раз вызвано событие? Да и сама «одноразовость» ограничена лишь спецификацией, кем-то придуманной, потому что так хорошо ложилось в концепцию. Я же говорю о том, что это не единственно возможная концепция.

                                Для меня лично одноразовость это минус. Именно по этому на каждый «then» создается новый объект (+несколько областей видимости). Это так же порождает и недостатки, указанные в посте. EventEmitter никогда не проглотит ошибку. И ещё куча минусов есть.
                              0
                              Promise.all() не имеет отношения к параллелизму. Тут речь лишь о том, что порядок вычисления не важен (асинхронности в смысле). С параллелизмом в js вообще все сложно.

                              В jQuery реализация promise A (без плюса), от чего на практике одни минусы. Сталкивался с тем, что объекту Deferred можно сделать reject(), после чего resolve() и обещание внезапно становится выполненным.
                                0
                                Скажите, пожалуйста, правильно ли я понимаю, что вызвать разрешение обещания можно только внутри функции, переданной аргументом в конструктор? То есть нельзя так же, как в jQuery, создать deferred-объект и потом где-нибудь, в произвольной точке кода, вызвать его разрешение?
                                  0
                                  Никто конечно не мешает вытащить функции разрешения наружу. Но это как раз и является лично для меня раздражающим фактором в конструкции Q.defer(), т.к. в случае с Revealing Constructor Pattern (т.е. как реализован Promise), разрешение возможно только внутри, что в большинстве случаев является необходимым и достаточным. Затрудняюсь придумать сценарий, когда Promise создается в одном месте, а резолвится в другом.
                                    0
                                    Ну вот у нас часто применяется примерно такой подход:

                                    let deferred1=$.deferred(), deferred2=$.deferred();
                                    function callbackFn1(arr){
                                    …//обработка ответа № 1 сервера
                                    deferred1.resolve(arr);
                                    }
                                    function callbackFn2(arr){
                                    …//обработка ответа № 2 сервера
                                    deferred2.resolve(arr);
                                    }
                                    function callbackFn3(arr1, arr2){
                                    …//обработка, которой нужно оба ответа сервера
                                    }
                                    $.deferred().when(deferred1, deferred2).then(callbackFn3);
                                    //в какой-то момент инициируется запрос № 1
                                    $.request(AJAX-запрос_1, callbackFn1);
                                    //а в какой-то другой момент независимо инициируется запрос № 2
                                    $.request(AJAX-запрос_2, callbackFn2);
                                    


                                    И как это переписать на промисы? Как-то, наверное, можно, но не похоже, чтобы это было удобно.
                                      0
                                      У вас спагетти получилось :-)

                                      Я бы сделал так:

                                      // первый ленивый загрузчик данных
                                      var first = $jin.atom.prop({
                                          pull: atom => {
                                              $.request(AJAX-запрос_1, arr => atom.push( arr ) )
                                              throw new $jin.atom.wait( 'Request 1' )
                                          }
                                      })
                                      
                                      // второй ленивый загрузчик данных
                                      var second = $jin.atom.prop({
                                          pull: atom => {
                                              $.request(AJAX-запрос_2, arr => atom.push( arr ) )
                                              throw new $jin.atom.wait( 'Request 2' )
                                          }
                                      })
                                      
                                      // ленивый запускатель обеих реквестов параллельно и рендерер результата в документ
                                      var result = $jin.atom.prop({
                                          pull: atom => {
                                              var data = $jin.atom.get([ first , second ])
                                              return data[0].concat( data[1] )
                                          },
                                          notify: ( atom , next ) => document.body.innerHTML = next.join( '|' )
                                      })
                                      
                                      // запускаем синхронизацию сервера с клиентом
                                      result.pull()
                                      
                                      // обновляем первую пачку данных через 10 секунд
                                      setTimeout( () => first.update() , 10000 )
                                      
                                        0
                                        Когда ж у меня найдется время Ваши атомы потестировать…

                                        У меня есть просьба — можно статью о том, как использовать атомы в приложении на React, а еще лучше — с учетом Flux. По-моему, отлично бы вписалось. Например чтоб можно было сравнить с github.com/facebook/flux/tree/master/examples/flux-chat.
                                          0
                                          Пока что могу разве что написать как их использовать с AngularJS :-) С реактом очень слабо знаком.
                                            0
                                            Обещаю по аналогии с Angular написать для React, я и с тем и с тем знаком :) баш на баш
                                              0
                                              По курам :-)
                                  +1
                                  в функции timeout не хватает promise.catch(reject); или я не прав?
                                    0
                                    Он там совершенно ни к чему, ловить мы внутри функции ничего не планируем.
                                      +1
                                      timeout(Promise.reject(), 1e4).catch(function(){
                                        console.log('Да ладно? Мало того, что зря ждём таймаут, так ещё и unhandled rejection %)');
                                      });
                                      
                                        0
                                        UPD. Я понял, о чем речь была.

                                        function timeout(promise, ms) {
                                            return new Promise(function (resolve, reject) {
                                                promise.then(resolve).catch(reject); // вот так
                                                setTimeout(function () {
                                                    reject(new Error('Timeout’));
                                                }, ms);
                                            });
                                        }
                                        

                                  Only users with full accounts can post comments. Log in, please.