Как создать функцию, которая возвращает существующее обещание вместо нового обещания?

Эксперты JavaScript/Promise,

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

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

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

Мое предположение на данный момент; чтобы сделать этот хороший код, функция должна возвращать существующее обещание вместо нового, но если другие решения предоставляют ту же функциональность и более приятный код, сообщите мне об этом. Родной ES6 предпочтительнее.

Спасибо!

-

'use strict';

var p;
var q;

var randomNumber = function() {
  return new Promise(function(resolve) {
    console.log(`p: ${p}`);

    if (typeof p === 'undefined') {
      setTimeout(function() {
        p = Math.random();
        resolve(p);
      }, 1000);

    } else {
      resolve(p);
    }
  });
};

var randomNumberPlusOne = function() {
  return new Promise(function(resolve) {
    console.log(`q: ${q}`);

    if (typeof q === 'undefined') {
      randomNumber()
        .then(function(p) {
          q = p + 1;
          resolve(q);
        });

    } else {
      resolve(q);
    }
  });
};

var showResult = function(result) {
  console.log();
  console.log(`r: ${result}`);
};

randomNumber()
  .then(showResult);

randomNumber()
  .then(randomNumberPlusOne)
  .then(showResult);

randomNumberPlusOne()
  .then(showResult);

setTimeout(function() {
  console.log();
  console.log('setTimeout:');
  console.log();
  randomNumber()
    .then(showResult);

  randomNumber()
    .then(randomNumberPlusOne)
    .then(showResult);

  randomNumberPlusOne()
    .then(showResult);
}, 2000);

- (код ниже основан на отзывах Берги;

'use strict';

var randomNumber = (function() {
  var p = null;

  return function() {
    console.log('p:');
    console.log(p);
    console.log();

    if (!p) {
      p = new Promise(function(resolve) {
        setTimeout(function() {
          resolve(Math.random());
        }, 1000);
      });
    }

    return p;
  };
})();

var randomNumberPlusOne = (function() {
  var q = null;

  return function() {
    console.log('q:');
    console.log(q);
    console.log();

    if (!q) {
      q = randomNumber()
        .then(function(p) {
          return p + 1;
        });
    }

    return q;
  };
})();

var showResult = function(result) {
  console.log(`r: ${result}`);
  console.log();
};

randomNumber()
  .then(showResult);

randomNumber()
  .then(randomNumberPlusOne)
  .then(showResult);

randomNumberPlusOne()
  .then(showResult);

setTimeout(function() {
  console.log();
  console.log('setTimeout:');
  console.log();

  randomNumber()
    .then(showResult);

  randomNumber()
    .then(randomNumberPlusOne)
    .then(showResult);

  randomNumberPlusOne()
    .then(showResult);

}, 2000);

person JoHe    schedule 04.08.2015    source источник
comment
Используйте переменную. Вот почему существуют переменные. Таким образом, вы можете ссылаться на одни и те же данные несколько раз.   -  person    schedule 05.08.2015
comment
var x = новое обещание(); функция() {возврат х};   -  person Melbourne2991    schedule 05.08.2015
comment
Спасибо за ваш ответ Джеймен. Возможно, я неправильно понял ваш ответ, но когда вы используете обещание в качестве переменной вместо функции, код выполняется немедленно, а не ожидает вызова при необходимости. Что приводит к нежелательным побочным эффектам. Кроме того, метод then нуждается в функции вместо обещания, чтобы предотвратить провал результата обещания.   -  person JoHe    schedule 05.08.2015
comment
Подождите, значит, вы ожидаете, что randomNumber и randomNumberPlusOne будут возвращать одно и то же число при каждом вызове, не случайное число???   -  person Bergi    schedule 05.08.2015


Ответы (2)


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

var p = null;
function notSoRandomAsyncNumber() {
  if (!p)
    p = new Promise(function(resolve) {
      setTimeout(function() {
        resolve(Math.random());
      }, 1000);
    });
  return p;
}

Или, абстрагировавшись во вспомогательную функцию:

function memoize(fn) {
  var cache = null;
  return function memoized(args) {
    if (fn) {
      cache = fn.apply(this, arguments);
      fn = null;
    }
    return cache;
  };
}
function randomAsyncNumber() {
  return new Promise(res => {
    setTimeout(() => resolve(Math.random()), 1000);
  });
}
function randomAsyncNumberPlusOne() {
  return randomAsyncNumber().then(n => n+1);
}
var notSoRandomAsyncNumber = memoize(randomAsyncNumber);
var notSoRandomAsyncNumberPlusOne = memoize(randomAsyncNumberPlusOne);

(обратите внимание, что notSoRandomAsyncNumberPlusOne по-прежнему будет создавать randomAsyncNumber() при первом вызове, а не notSoRandomAsyncNumber())

person Bergi    schedule 04.08.2015
comment
Спасибо за отличный ответ, Берги! Я все еще новичок в программировании и JavaScript и не знал, что этот метод называется мемоизацией. После вашего ответа я чувствую себя глупо из-за того, что возвращаю значение вместо обещания. Основываясь на вашем ответе, я создал доказательство концепции, основанное на замыканиях, чтобы удалить переменные из глобальной области. Каково ваше мнение об этом методе? (Мне нужно было добавить новый код к вопросу, потому что мне не разрешили вставить комментарий, я также новичок в переполнении стека) - person JoHe; 05.08.2015
comment
Да, с помощью IEFE можно создать область для p и q. Только это кажется немного повторяющимся, для этого я и написал memoize :-) - person Bergi; 05.08.2015
comment
ах, теперь нуб понимает, что вы также использовали замыкания. ;-) (еще один момент, который я чувствую глупо) Еще раз спасибо и, надеюсь, однажды у меня будут ваши навыки JavaScript, и я также смогу помочь людям. - person JoHe; 05.08.2015

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

person Paul Marrington    schedule 04.08.2015