Является ли javascript Promise API более запутанным, чем нужно?

Думаю, мне наконец-то удалось сконцентрироваться на javascript/ES6 Promises, по большей части. Это было нелегко! Но что-то меня смущает в дизайне.

Почему конструктор Promise принимает обратный вызов? Учитывая, что обратный вызов вызывается немедленно, не может ли вызывающая сторона просто выполнить этот код вместо этого, тем самым избегая одного ненужного уровня умопомрачительного «не звони мне, я позвоню тебе»?

Вот то, что я считаю прототипом использования Promise, скопированное из учебника Джейка Арчибальда по Javascript Promises http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promisifying-xmlhttprequest с удаленными комментариями.

Это оболочка на основе Promise для XMLHttpRequest GET-запроса:

function get(url) {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      }
      else {
        reject(Error(req.statusText));
      }
    };
    req.onerror = function() {
      reject(Error("Network Error"));
    };
    req.send();
  });
}

Для меня приведенный выше код был бы намного проще для понимания, если бы он был переписан следующим образом, используя немного другой тип обещания, который я себе представляю, имея конструктор без аргументов и методы разрешения/отклонения:

function get(url) {
  var promise = new MyEasierToUnderstandPromise();
  var req = new XMLHttpRequest();
  req.open('GET', url);
  req.onload = function() {
    if (req.status == 200) {
      promise.resolve(req.response);
    }
    else {
      promise.reject(Error(req.statusText));
    }
  };
  req.onerror = function() {
    promise.reject(Error("Network Error"));
  };
  req.send();
  return promise;
}

MyEasierToUnderstandPromise не так уж сложно реализовать с точки зрения Promise. Сначала я попытался сделать его настоящим подклассом Promise, но по какой-то причине не смог заставить это работать; поэтому вместо этого я реализовал его как простую фабричную функцию, которая возвращает простой старый объект Promise с парой присоединенных дополнительных функций, которые ведут себя как функции-члены:

function NewMyEasierToUnderstandPromise() {
  var resolveVar;
  var rejectVar;
  var promise = new Promise(function(resolveParam, rejectParam) {
    resolveVar = resolveParam;
    rejectVar = rejectParam;
  });
  promise.resolve = resolveVar;
  promise.reject = rejectVar;
  return promise;
};

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

Я знаю, что много умных людей приложили руку к созданию Promise API, и все, кажется, в целом счастливы и гордятся этим, поэтому мне интересно, что они думали.


person Don Hatch    schedule 13.02.2016    source источник
comment
ваш easyToUnderstandPromise в некотором роде похож на jQuery.Deferred. С вашим дизайном возвращаемое обещание обязательно раскрывает методы разрешения/отклонения. Я где-то читал, почему это плохо, но не могу найти этот ресурс (прошли годы с тех пор, как я его читал)   -  person Jaromanda X    schedule 13.02.2016
comment
Прочтите об асинхронности/ожидании ES7.   -  person Michał Perłakowski    schedule 13.02.2016
comment
Существует также отложенный шаблон, но он устарел по уважительной причине   -  person Bergi    schedule 06.03.2016


Ответы (3)


Ваша версия не защищена от исключений, в то время как Promises/A+ безопасны, поскольку они перехватываются конструктором Promise.

person Amit    schedule 13.02.2016
comment
Отличный ответ, спасибо. Я думаю, что все вводные документы и учебные пособия были бы значительно улучшены, если бы я упомянул об этом - мой мозг просто не хочет поглощать вещи, для которых он не видит причины! - person Don Hatch; 13.02.2016

Обещания предназначены для использования в качестве значений. Подход конструктора ES инкапсулирует создание обещания, которое затем можно передавать как значение. Когда это значение передается, потребителю этого значения не нужны resolve и reject, поэтому эти функции не должны быть частью общедоступного API.

(и все, что связано с обработкой исключений и цепочками)

person rich remer    schedule 13.02.2016

В качестве дополнительной информации, когда скрипт определяет цепочку обещаний ES6, например

var promise = new Promise(executor).then(something).catch(handleError);

переменная обещания остается установленной на обещание, возвращаемое вызовом метода .catch. Вся цепочка или объекты Promise фактически защищены от сбора мусора с помощью ссылок на функции разрешения/отклонения, хранящихся в функции-исполнителе. Если вызывается разрешение (обычно асинхронно после возврата исполнителя, но до того, как функции разрешения/отклонения выходят за пределы области действия), вызывается then прослушиватель «что-то», при этом внутренний исполнитель платформы Promise содержит ссылки на функции разрешения/отклонения для обещания, возвращенного вызов then, чтобы предотвратить преждевременную сборку мусора, а также любые последующие связанные промисы.

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

var promise = new Promise(); // no executor
promise.then(something).catch(handleError);
initiateOperation( promise);

а затем асинхронно в коде ответа операции

promise.resolve(value);  // if no error occurred
promise = null;          // explicit discard of first promise reference to allow GC?

Общий минималистичный подход обещаний ES6 теперь начинает выглядеть многообещающе (ой). Я сочувствую вашим трудностям в изучении того, как работают обещания - интересное путешествие!!!

person traktor    schedule 13.02.2016
comment
Нет, вам не нужно явно отбрасывать ссылки на promise в асинхронном обратном вызове, точно так же, как вам не нужно явно отбрасывать ссылки на resolve/reject в области действия исполнителя. - person Bergi; 06.03.2016