5

Как получить доступ к колбэкам разрешения Promise вне области видимости колбэка конструктора Promise?

15

У меня возникла проблема с использованием Promise в JavaScript. Обычно я создаю и использую Promise следующим образом:

new Promise((resolve, reject) => {
  const obj = new MyEventEmitter();
  obj.onsuccess = (event) => { resolve(event.result); };
  obj.onerror = (event) => { reject(event.error); };
});

Однако недавно я попытался вынести функцию разрешения (resolver) за пределы коллбэка исполнителя (executor) для большей гибкости, и мой код стал выглядеть так:

let outsideResolve;
let outsideReject;
new Promise((resolve, reject) => {
  outsideResolve = resolve; 
  outsideReject = reject; 
});

А позже, в обработчике события:

onClick = function() {
  outsideResolve();
}

Это работает нормально, но возникает вопрос: есть ли более простой способ сделать это? Если нет, то является ли такой подход хорошей практикой?

5 ответ(ов)

2

Ваш код создает новый объект Promise, однако вы вызываете promiseResolve() сразу же после его создания. Это означает, что промис будет выполнен немедленно и его состояние станет "выполнено" (fulfilled).

Вот перевод вашего кода с объяснением:

var promiseResolve, promiseReject;

var promise = new Promise(function(resolve, reject){
  promiseResolve = resolve; // Сохраняем ссылку на функцию resolve
  promiseReject = reject;   // Сохраняем ссылку на функцию reject
});

promiseResolve(); // Тут сразу вызываем resolve

Обратите внимание, что если вы хотите использовать promiseReject в будущем (например, для обработки ошибок), вам необходимо не вызывать promiseResolve() сразу. Это может привести к неожиданному поведению, если вы планируете выполнять какие-то асинхронные операции перед завершением промиса.

Если у вас есть дополнительные вопросы о поведении промисов или асинхронных операциях, не стесняйтесь спрашивать!

0

Я согласен с ответом @JonJaques, но хотел бы немного его развить.

Если вы свяжете методы then и catch с объектом Deferred, то этот объект полностью реализует API Promise, и вы сможете обращаться с ним как с обещанием, используя await и подобные конструкции.

⚠️ Примечание редактора: Я не рекомендую пользоваться подобным паттерном, поскольку на момент написания Promise.prototype.finally еще не существовало. Сейчас это уже реализовано, и могут появляться и другие методы, поэтому я рекомендую вам дополнять экземпляр промиса функциями resolve и reject вместо этого:

function createDeferredPromise() {
  let resolve;
  let reject;

  const promise = new Promise((thisResolve, thisReject) => {
    resolve = thisResolve;
    reject = thisReject;
  });

  return Object.assign(promise, { resolve, reject });
}

Поддержите чужой ответ.

А вот пример с классом DeferredPromise:

class DeferredPromise {
  constructor() {
    this._promise = new Promise((resolve, reject) => {
      // присваиваем функции resolve и reject текущему контексту
      // чтобы они были доступны из экземпляра класса
      this.resolve = resolve;
      this.reject = reject;
    });
    // связываем методы `then`, `catch` и `finally`, чтобы реализовать тот же интерфейс, что и у Promise
    this.then = this._promise.then.bind(this._promise);
    this.catch = this._promise.catch.bind(this._promise);
    this.finally = this._promise.finally.bind(this._promise);
    this[Symbol.toStringTag] = 'Promise';
  }
}

const deferred = new DeferredPromise();
console.log('ждем 2 секунды...');
setTimeout(() => {
  deferred.resolve('вау!');
}, 2000);

async function someAsyncFunction() {
  const value = await deferred;
  console.log(value);
}

someAsyncFunction();

В этом примере класс DeferredPromise создает промис, на который можно подписываться, а также предоставляет возможность вручную выполнять его (разрешать или отклонять).

0

В 2015 году я разработал решение для своего фреймворка, которое назвал Task. Вот как это выглядит:

function createPromise(handler){
  var resolve, reject;

  var promise = new Promise(function(_resolve, _reject){
    resolve = _resolve; 
    reject = _reject;
    if(handler) handler(resolve, reject);
  });
  
  promise.resolve = resolve;
  promise.reject = reject;
  return promise;
}

// Создание промиса
var promise = createPromise();
promise.then(function(data){ alert(data); });

// Разрешение промиса снаружи
promise.resolve(200);

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

0

Ответ, который вы приняли, неверен. На самом деле, решение проще, если использовать область видимости и ссылки, хотя это может встревожить долговязких поклонников Promise:

const createPromise = () => {
    let resolver;
    return [
        new Promise((resolve, reject) => {
            resolver = resolve;
        }),
        resolver,
    ];
};

const [ promise, resolver ] = createPromise();
promise.then(value => console.log(value));
setTimeout(() => resolver('foo'), 1000);

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

Через секунду в консоли будет выведено:

> foo

Таким образом, этот подход позволяет нам контролировать разрешение промиса вне его самого.

0

Если кто-то ищет реализацию утилиты для упрощения данной задачи на TypeScript, вот пример:

export const deferred = <T>() => {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: unknown) => void;
  const promise = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });
  return { resolve, reject, promise };
};

Эту утилиту можно использовать следующим образом:

const { promise, resolve } = deferred<string>();

promise.then((value) => console.log(value)); // ничего не происходит

resolve('foo'); // вывод: foo

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь