Устранение 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: тайм-аут