Устранение Javascript Promise вне функции
Я использую ES6 Promise.
Обычно, Promise создается и используется как
new Promise(function(resolve, reject){
if (someCondition){
resolve();
} else {
reject();
}
});
Но я делал что-то вроде ниже, чтобы принять решение снаружи ради гибкости.
var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) {
outsideResolve = resolve;
outsideReject = reject;
});
И позже
onClick = function(){
outsideResolve();
}
Это прекрасно работает, но есть ли более простой способ сделать это? Если нет, это хорошая практика?
Ответы
Ответ 1
Нет, нет другого способа сделать это - единственное, что я могу сказать, это то, что этот случай использования не очень распространен. Как сказал Феликс в комментарии - то, что вы делаете, будет последовательно работать.
Стоит отметить, что причина, по которой конструктор обещаний ведет себя таким образом, - это бросить безопасность - если исключение, которого вы не ожидали, происходит, когда ваш код работает внутри конструктора обещаний, он превратится в отказ, эта форма безопасности броска - преобразование брошенные ошибки для отказов важны и помогают поддерживать предсказуемый код.
Для этой причины безопасности, конструктор обещаний был выбран по сравнению с отсрочками (которые являются альтернативным способом создания обещаний, которые позволяют делать то, что вы делаете) - что касается лучших практик - я бы передал элемент и использовал конструктор обещаний вместо:
var p = new Promise(function(resolve, reject){
this.onclick = resolve;
}.bind(this));
По этой причине - всякий раз, когда вы можете использовать конструктор обещаний для экспорта функций, я рекомендую вам использовать его. Всякий раз, когда вы можете избежать обоих - избегайте обоих и цепи.
Обратите внимание, что вы никогда не должны использовать конструктор обещаний для таких вещей, как if(condition)
, первый пример может быть записан как:
var p = Promise[(someCondition)?"resolve":"reject"]();
Ответ 2
просто:
var promiseResolve, promiseReject;
var promise = new Promise(function(resolve, reject){
promiseResolve = resolve;
promiseReject = reject;
});
promiseResolve();
Ответ 3
Бит опоздал на вечеринку, но другой способ сделать это - использовать объект Deferred. Вы, по сути, имеете одинаковое количество шаблонов, но это удобно, если вы хотите передать их и, возможно, решить за пределами их определения.
Наивная реализация:
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject)=> {
this.reject = reject
this.resolve = resolve
})
}
}
function asyncAction() {
var dfd = new Deferred()
setTimeout(()=> {
dfd.resolve(42)
}, 500)
return dfd.promise
}
asyncAction().then(result => {
console.log(result) // 42
})
Версия ES5:
function Deferred() {
var self = this;
this.promise = new Promise(function(resolve, reject) {
self.reject = reject
self.resolve = resolve
})
}
function asyncAction() {
var dfd = new Deferred()
setTimeout(function() {
dfd.resolve(42)
}, 500)
return dfd.promise
}
asyncAction().then(function(result) {
console.log(result) // 42
})
Ответ 4
Решение, которое я придумал в 2015 году для своих фреймворков. Я назвал этот тип обещаний Задача
function createPromise(handler){
var _resolve, _reject;
var promise = new Promise(function(resolve, reject){
_resolve = resolve;
_reject = reject;
handler(resolve, reject);
})
promise.resolve = _resolve;
promise.reject = _reject;
return promise;
}
var promise = createPromise()
promise.then(function(data){ alert(data) })
promise.resolve(200) // resolve from outside
Ответ 5
Мне понравился ответ @JonJaques, но я хотел сделать еще один шаг.
Если вы привязываете then
и catch
к объекту Deferred
, тогда он полностью реализует API Promise
, и вы можете рассматривать его как обещание и await
его и т.д.
class DeferredPromise {
constructor() {
this._promise = new Promise((resolve, reject) => {
// assign the resolve and reject functions to `this`
// making them usable on the class instance
this.resolve = resolve;
this.reject = reject;
});
// bind `then` and `catch` to implement the same interface as Promise
this.then = this._promise.then.bind(this._promise);
this.catch = this._promise.catch.bind(this._promise);
this[Symbol.toStringTag] = 'Promise';
}
}
const deferred = new DeferredPromise();
console.log('waiting 2 seconds...');
setTimeout(() => {
deferred.resolve('whoa!');
}, 2000);
async function someAsyncFunction() {
const value = await deferred;
console.log(value);
}
someAsyncFunction();
Ответ 6
Вспомогательный метод облегчит эти дополнительные накладные расходы и даст вам то же самое чувство jQuery.
function Deferred() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
Использование будет
const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
confirm: resolve,
cancel: reject
});
return promise;
Что похоже на jQuery
const dfd = $.Deferred();
displayConfirmationDialog({
confirm: dfd.resolve,
cancel: dfd.reject
});
return dfd.promise();
Хотя, в случае использования этого простого, родного синтаксиса хорошо
return new Promise((resolve, reject) => {
displayConfirmationDialog({
confirm: resolve,
cancel: reject
});
});
Ответ 7
Я использую вспомогательную функцию, чтобы создать то, что я называю "плоским обещанием" -
function flatPromise() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
И я использую это так -
function doSomethingAsync() {
// Get your promise and callbacks
const { resolve, reject, promise } = flatPromise();
// Do something amazing...
setTimeout(() => {
resolve('done!');
}, 500);
// Pass your promise to the world
return promise;
}
Смотрите полный рабочий пример -
function flatPromise() {
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve, reject };
}
function doSomethingAsync() {
// Get your promise and callbacks
const { resolve, reject, promise } = flatPromise();
// Do something amazing...
setTimeout(() => {
resolve('done!');
}, 500);
// Pass your promise to the world
return promise;
}
(async function run() {
const result = await doSomethingAsync()
.catch(err => console.error('rejected with', err));
console.log(result);
})();
Ответ 8
Вы можете обернуть Обещание в классе.
class Deferred {
constructor(handler) {
this.promise = new Promise((resolve, reject) => {
this.reject = reject;
this.resolve = resolve;
handler(resolve, reject);
});
this.promise.resolve = this.resolve;
this.promise.reject = this.reject;
return this.promise;
}
promise;
resolve;
reject;
}
// How to use.
const promise = new Deferred((resolve, reject) => {
// Use like normal Promise.
});
promise.resolve(); // Resolve from any context.
Ответ 9
Да, ты можешь. Используя API CustomEvent
для среды браузера. И использование проекта генератора событий в среде node.js. Поскольку фрагмент в вопросе относится к среде браузера, вот рабочий пример для того же.
function myPromiseReturningFunction(){
return new Promise(resolve => {
window.addEventListener("myCustomEvent", (event) => {
resolve(event.detail);
})
})
}
myPromiseReturningFunction().then(result => {
alert(result)
})
document.getElementById("p").addEventListener("click", () => {
window.dispatchEvent(new CustomEvent("myCustomEvent", {detail : "It works!"}))
})
<p id="p"> Click me </p>
Ответ 10
Наше решение заключалось в том, чтобы использовать блокировки для хранения функций разрешения/отклонения и дополнительно прикреплять функцию для расширения самого обещания.
Вот шаблон:
function getPromise() {
var _resolve, _reject;
var promise = new Promise((resolve, reject) => {
_reject = reject;
_resolve = resolve;
});
promise.resolve_ex = (value) => {
_resolve(value);
};
promise.reject_ex = (value) => {
_reject(value);
};
return promise;
}
И используя его:
var promise = getPromise();
promise.then(value => {
console.info('The promise has been fulfilled: ' + value);
});
promise.resolve_ex('hello');
// or the reject version
//promise.reject_ex('goodbye');
Ответ 11
Я написал небольшую библиотеку для этого. https://www.npmjs.com/package/@inf3rno/promise.exposed
Я использовал подход фабричного метода, который писали другие, но я переопределил методы then
, catch
, finally
также, чтобы вы также могли разрешить первоначальное обещание.
Разрешение Обещания без исполнителя со стороны:
const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");
Гонки с исполнителем setTimeout извне:
const promise = Promise.exposed(function (resolve, reject){
setTimeout(function (){
resolve("I almost fell asleep.")
}, 100000);
}).then(console.log);
setTimeout(function (){
promise.resolve("I don't want to wait that much.");
}, 100);
Существует режим без конфликтов, если вы не хотите загрязнять глобальное пространство имен:
const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");
Ответ 12
Многие ответы здесь похожи на последний пример в этой статье. Я кеширую несколько Promises, и функции resolve()
и reject()
могут быть назначены любой переменной или свойству. В результате я могу сделать этот код немного более компактным:
function defer(obj) {
obj.promise = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject = reject;
});
}
Вот упрощенный пример использования этой версии defer()
для объединения загрузки Promise FontFace
с другим асинхронным процессом:
function onDOMContentLoaded(evt) {
let all = []; // array of Promises
glob = {}; // global object used elsewhere
defer(glob);
all.push(glob.promise);
// launch async process with callback = resolveGlob()
const myFont = new FontFace("myFont", "url(myFont.woff2)");
document.fonts.add(myFont);
myFont.load();
all.push[myFont];
Promise.all(all).then(() => { runIt(); }, (v) => { alert(v); });
}
//...
function resolveGlob() {
glob.resolve();
}
function runIt() {} // runs after all promises resolved
Обновление: 2 варианта, если вы хотите инкапсулировать объект:
function defer(obj = {}) {
obj.promise = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject = reject;
});
return obj;
}
let deferred = defer();
а также
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
let deferred = new Deferred();
Ответ 13
Принятый ответ неверен. Это довольно легко, используя область видимости и ссылки, хотя это может разозлить пуристов 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);
По сути, мы получаем ссылку на функцию разрешения при создании обещания и возвращаем его, чтобы его можно было установить извне.
Через секунду консоль выведет:
> foo
Ответ 14
Для вашего варианта использования определенно лучший способ.
const promise = new Promise(function(resolve, reject){
if (someCondition){
resolve()
} else {
reject()
}
})
эквивалентно:
const promise = Promise[someCondition ? 'resolve' : 'reject']()
а затем позже:
onClick = function(){
promise
.then(() => /** handle resolve */)
.catch(err => /** handle reject */)
}
Promises полезны для работы async, и этот пример не работает async, делая его анти-шаблоном. Лучше использовать promises с такими реализациями, как fetch, которые возвращают изначально promises и используют стиль конструктора только при работе с несовместимым асинхронным интерфейсом (node обратные вызовы).
Самый чистый код не является кодом вообще.
Ответ 15
Как насчет создания функции, чтобы захватить отклонение и вернуть его?
function createRejectablePromise(handler) {
let _reject;
const promise = new Promise((resolve, reject) => {
_reject = reject;
handler(resolve, reject);
})
promise.reject = _reject;
return promise;
}
// Usage
const { reject } = createRejectablePromise((resolve) => {
setTimeout(() => {
console.log('resolved')
resolve();
}, 2000)
});
reject();
Ответ 16
Я собрал суть, которая делает эту работу: https://gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13
вот как вы должны это использовать:
import ExternalizedPromiseCreator from '../externalized-promise';
describe('ExternalizedPromise', () => {
let fn: jest.Mock;
let deferredFn: jest.Mock;
let neverCalledFn: jest.Mock;
beforeEach(() => {
fn = jest.fn();
deferredFn = jest.fn();
neverCalledFn = jest.fn();
});
it('resolve should resolve the promise', done => {
const externalizedPromise = ExternalizedPromiseCreator.create(() => fn());
externalizedPromise
.promise
.then(() => deferredFn())
.catch(() => neverCalledFn())
.then(() => {
expect(deferredFn).toHaveBeenCalled();
expect(neverCalledFn).not.toHaveBeenCalled();
done();
});
expect(fn).toHaveBeenCalled();
expect(neverCalledFn).not.toHaveBeenCalled();
expect(deferredFn).not.toHaveBeenCalled();
externalizedPromise.resolve();
});
...
});
Ответ 17
сначала включите --allow-natives-синтаксис в браузере или на узле
const p = new Promise(function(resolve, reject){
if (someCondition){
resolve();
} else {
reject();
}
});
onClick = function () {
%ResolvePromise(p, value)
}
Ответ 18
Я создал библиотеку под названием manual-promise
, которая заменяет Promise
. Ни один из других ответов здесь не будет работать в качестве замены для Promise
, поскольку они используют прокси или оболочки.
yarn add manual-promise
npn install manual-promise
import { ManualPromise } from "manual-promise";
const prom = new ManualPromise();
prom.resolve(2);
// actions can still be run inside the promise
const prom2 = new ManualPromise((resolve, reject) => {
// ... code
});
new ManualPromise() instanceof Promise === true
https://github.com/zpxp/manual-promise#readme
Ответ 19
Я придумал что-то, что является в основном минимальным вариантом Javascript wait/notify pattern (без защиты критического раздела, поскольку Javascript не есть этот вопрос).
Заклинание здесь.
код:
// create some monitors
var monitors = [
new Monitor(1), // rough deadline
new Monitor(),
new Monitor(),
new Monitor()
];
// register event handlers (on "notify event")
for (var i = 0; i < monitors.length; ++i) {
var monitor = monitors[i];
monitor.i = i;
monitor.wait
.bind(monitor)
.then(function(result) {
console.log('Success ' + this.i + ': ' + result);
})
.catch(function(err) {
console.error('Failure ' + this.i + ': ' + err);
});
}
// notify like a bawss
Promise
.delay(100)
.then(function() {
monitors[0].notifyResolve('hi! :)');
monitors[1].notifyReject('hi! :(');
monitors[2].notifyResolve('hi! :)');
// forgot about the fourth monitor: it'll timeout
});
// Monitor class
function Monitor(timeoutMillis) {
timeoutMillis = timeoutMillis || 1000;
var resolve, reject;
var isResolved = false, err, result;
var promise = new Promise(function(_resolve, _reject) {
if (isResolved) {
if (err) {
_reject(err)
}
else {
_resolve(result);
}
}
else {
resolve = _resolve;
reject = _reject;
}
});
// make sure, promise will be fulfilled
if (timeoutMillis >= 0) { // negative value means: no timeout
setTimeout(function() {
if (!isResolved) {
this.notifyReject('timeout');
}
}.bind(this), timeoutMillis);
}
this.wait = promise;
this.notifyResolve = function(_result) {
if (isResolved) return;
isResolved = true;
if (resolve) {
resolve(_result);
}
else {
// remember result until Promise ctor callback is called
result = _result;
}
};
this.notifyReject = function(_err) {
if (isResolved) return;
isResolved = true;
if (reject) {
reject(_err);
}
else {
// remember result until Promise ctor callback is called
err = _err;
}
};
};
Результат:
Отказ 0: тайм-аут
Успех 2: привет!:)
Отказ 1: привет!: (
Отказ 3: тайм-аут