Как получить доступ к колбэкам разрешения Promise вне области видимости колбэка конструктора Promise?
У меня возникла проблема с использованием 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 ответ(ов)
Ваш код создает новый объект Promise, однако вы вызываете promiseResolve()
сразу же после его создания. Это означает, что промис будет выполнен немедленно и его состояние станет "выполнено" (fulfilled).
Вот перевод вашего кода с объяснением:
var promiseResolve, promiseReject;
var promise = new Promise(function(resolve, reject){
promiseResolve = resolve; // Сохраняем ссылку на функцию resolve
promiseReject = reject; // Сохраняем ссылку на функцию reject
});
promiseResolve(); // Тут сразу вызываем resolve
Обратите внимание, что если вы хотите использовать promiseReject
в будущем (например, для обработки ошибок), вам необходимо не вызывать promiseResolve()
сразу. Это может привести к неожиданному поведению, если вы планируете выполнять какие-то асинхронные операции перед завершением промиса.
Если у вас есть дополнительные вопросы о поведении промисов или асинхронных операциях, не стесняйтесь спрашивать!
Я согласен с ответом @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
создает промис, на который можно подписываться, а также предоставляет возможность вручную выполнять его (разрешать или отклонять).
В 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
добавляются к объекту промиса, что дает возможность вызывать их из любой части вашего кода.
Ответ, который вы приняли, неверен. На самом деле, решение проще, если использовать область видимости и ссылки, хотя это может встревожить долговязких поклонников 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
Таким образом, этот подход позволяет нам контролировать разрешение промиса вне его самого.
Если кто-то ищет реализацию утилиты для упрощения данной задачи на 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
Эта реализация позволяет создавать промисы, которые можно разрешать или отклонять вручную, что может быть полезно в различных сценариях асинхронного программирования.
Использование async/await с циклом forEach
Синтаксис асинхронной стрелочной функции
Как преобразовать существующий API с обратными вызовами в промисы?
JavaScript Промисы - reject против throw
Использование async/await с методом Array.map