함수 범위 밖의 Javascript Promise 해결


279

ES6 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();
}

이것은 잘 작동하지만 더 쉬운 방법이 있습니까? 그렇지 않다면 이것이 좋은 습관입니까?


2
다른 방법이 없다고 생각합니다. 전달 된 콜백 Promise은 두 함수를 "내보내기"할 수 있도록 동 기적으로 실행되어야 한다고 지정 되어 있습니다.
Felix Kling

1
이것은 당신이 쓴 것처럼 정확하게 작동합니다. 내가 아는 한, 이것은 "정식적인"방법입니다.
Gilad Barner

14
앞으로 이것을 달성 할 수있는 공식적인 방법이 있어야한다고 생각합니다. 이 기능은 다른 맥락에서 가치를 기다릴 수 있기 때문에 매우 강력합니다.
호세

그들이이 문제에 대한 적절한 해결책을 제시 할 때마다 중첩 된 약속에도 적용되기를 바랍니다. 일부 약속은 되풀이 될 수 있습니다.
Arthur Tarasov

Promise API는 항상 반환 값으로 사용하고 액세스하거나 호출 할 수있는 객체로는 사용하지 않는 것이 "추천"이라고 생각합니다. 다시 말해, 우리가 접근 할 수있는 객체 나 호출 할 수있는 함수 나 변수로 참조하거나 매개 변수로 전달할 수있는 객체 대신에 값을 반환 값으로 취급하도록 강요합니다. 다른 객체로 약속을 사용하기 시작하면 아마도 당신의 질문처럼 외부에서 그것을 해결할 필요가 있습니다 ... 말하면서, 나는 또한 이것을하는 공식적인 방법이 있어야한다고 생각합니다 ... 그리고 연기 된 것은 나에게 해결 방법 인 것 같습니다.
Cancerbero

답변:


93

아니요, 다른 방법은 없습니다. 내가 말할 수있는 유일한 방법은이 사용 사례가 그리 일반적이지 않다는 것입니다. Felix가 의견에서 말했듯이 당신이하는 일은 일관되게 작동합니다.

약속 생성자가 이런 식으로 동작하는 이유는 던지기 안전입니다. 코드가 약속 생성자 내에서 실행되는 동안 예상치 못한 예외가 발생하면 거부,이 형식의 던지기 안전으로 변환됩니다. 거부는 중요하며 예측 가능한 코드를 유지하는 데 도움이됩니다.

이 던지기 안전상의 이유로, 약속 생성자는 지연된 것보다 선택되었습니다 (이것은 당신이하는 일을 허용하는 대체 약속 구성 방법입니다)-모범 사례와 마찬가지로 요소를 전달하고 대신 약속 생성자를 사용합니다.

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));

이러한 이유로- 함수를 내보내는 것보다 promise 생성자를 사용할 있을 때마다 사용하는 것이 좋습니다. 둘 다 피할 수있을 때마다-체인과 피하십시오.

약속 생성자를와 같은 용도로 사용해서는 안되며 if(condition)첫 번째 예제는 다음과 같이 작성할 수 있습니다.

var p = Promise[(someCondition)?"resolve":"reject"]();

2
안녕 벤자민! 약속이 언제 성취 될지 모른다면 현재 맛있는 약속 설탕을 얻는 더 좋은 방법이 있습니까? 일종의 비동기 대기 / 알림 패턴 처럼 ? 예를 들어 "store"와 같이 나중에 Promise체인을 호출 합니까? 예를 들어, 특정 경우에는 서버에 있으며 특정 클라이언트 응답 (클라이언트가 성공적으로 업데이트되었는지 확인하기 위해 SYN-ACK-kinda 핸드 셰이크)을 기다리고 있습니다.
Domi

1
@Domi q- 연결 및 RxJS를 확인하십시오.
Benjamin Gruenbaum

2
fetch API를 사용하여 어떻게 동일하게 할 수 있습니까?
Vinod Sobale

95
흔하지 않아? 거의 모든 프로젝트가 필요합니다.
Tomáš Zato-복원 모니카

1
유스 케이스에 관해서는 이벤트가 트리거되고 다른 일이 발생한 후에 무언가를 수행해야한다고 생각하십시오. 이벤트를 약속으로 변환하고 다른 약속과 통합하려고합니다. 나에게 일반적인 문제인 것 같습니다.
Gherman

130

단순한:

var promiseResolve, promiseReject;

var promise = new Promise(function(resolve, reject){
  promiseResolve = resolve;
  promiseReject = reject;
});

promiseResolve();

2
@ruX, 허용되는 답변에서 언급했듯이 의도적으로 설계되었습니다. 요점은 예외가 발생하면 약속 생성자가 포착한다는 것입니다. 이 답변 (및 광산)은 코드 호출에 대해 예외를 던질 가능성이 있습니다 promiseResolve(). 약속의 의미는 항상 값을 반환 한다는 것 입니다. 또한 이것은 기능적으로 OP의 게시물과 동일하며 재사용 가능한 방식으로 해결되는 문제는 없습니다.
Jon Jaques

4
@JonJaques 나는 당신이 말하는 것이 사실인지 확실하지 않습니다. 호출하는 코드 promiseResolve()는 예외를 발생시키지 않습니다. .catch생성자를 on으로 정의 할 수 있으며 어떤 코드에서 호출하더라도 생성자 .catch가 호출됩니다. jsbin은 이것이 어떻게 작동하는지 보여줍니다 : jsbin.com/yicerewivo/edit?js,console
carter

네, 다른 약속 생성자를 감싸서 잡았습니다. 정확히 내가 만들고자하는 요점입니다. 그러나 생성자 (일명 Deferred 객체) 외부에서 resolve ()를 호출하려고하는 다른 코드가 있다고 가정 해보십시오. 예외가 발생할 수 있으며 jsbin.com/cokiqiwapo/1/edit?js,console
Jon Jaques

8
나는 그것이 나쁜 디자인인지 확실하지 않습니다. 약속 밖에서 발생한 오류는 약속 내에서 잡히지 않아야합니다. 디자이너가 실제로 오류가 포착 될 것으로 예상 하는 경우 오해 또는 잘못된 이해의 예일 수 있습니다.
KalEl

3
이 정확한 구조는 이미 질문에 언급되어 있습니다. 당신은 그것을 읽었습니까?
Cedric Reichenbach

103

여기 파티에 늦었지만 연기 하는 또 다른 방법은 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
})

1
여기서 어휘 범위를 확인하십시오.
Florrie 2019

1
resolve|reject어휘 적으로 또는 통해 할당 되는지에 실질적인 차이는 없습니다 bind. 이것은 1.0 (ish)부터 사용 된 jQuery Deferred 객체 의 간단한 구현입니다 . 던지기 안전이 없다는 것을 제외하고는 약속과 똑같이 작동합니다. 이 질문의 핵심은 약속을 만들 때 몇 줄의 코드를 저장하는 방법이었습니다.
Jon Jaques

1
A는 연기 일반적인 방법을 사용하여이 작업을 수행하기 위해이 높지 않은 이유, 나도 몰라
BlueRaja - 대니 Pflughoeft

1
훌륭한 답변! jQuery가 제공하는 지연된 기능을 찾고있었습니다.
Anshul Koka

2
인가 Deferred되지?
Pacerier

19

내 프레임 워크에 대해 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

4
감사합니다. 그러나 핸들러 란 무엇입니까? 작동시키기 위해 제거해야했습니다.
Sahid 's

16

@JonJaques 답변을 좋아했지만 한 단계 더 나아가고 싶었습니다.

당신이 결합하는 경우 thencatch다음 Deferred객체를, 그것은 완전히 구현 PromiseAPI를 당신은 약속로 취급 할 수 있습니다 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();


10

도우미 메소드는 이러한 추가 오버 헤드를 완화하고 동일한 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
    });
});

8

헬퍼 함수를 ​​사용하여 "플랫 약속"이라고 부르는 것을 작성하고 있습니다.

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;

}

전체 작업 예보기-

편집 : flat-promise 라는 NPM 패키지를 만들었 으며 코드는 GitHub 에서도 사용할 수 있습니다 .


7

약속을 수업에 담을 수 있습니다.

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.

6

여기의 많은 답변은 이 기사 의 마지막 예와 비슷합니다 . 여러 약속을 캐싱하고 있으며 resolve()reject()기능을 모든 변수 또는 속성에 할당 할 수 있습니다. 결과적 으로이 코드를 약간 더 작게 만들 수 있습니다.

function defer(obj) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
}

다음은이 버전을 사용하여 로드 약속을 다른 비동기 프로세스와 defer()결합 하는 간단한 예입니다 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();

비동기 함수에서 이러한 예제를 사용하는 경우 해결 된 약속의 값을 사용하려면 promise 속성을 참조해야합니다.const result = await deferred.promise;
b00t

6

허용 된 답변이 잘못되었습니다. Promise purists를 화나게 할 수 있지만 범위와 참조를 사용하는 것은 매우 쉽습니다 .

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 함수에 대한 참조를 본질적으로 잡아서, 그것을 외부에서 설정할 수 있도록 반환합니다.

1 초 안에 콘솔은 다음을 출력합니다 :

> foo

이것이 최선의 방법이라고 생각합니다. 유일한 것은 코드가 조금 덜 장황 할 수 있다는 것입니다.
pie6k

좋은! 영리한 아이디어. 내가 할 수 있다면 +50
Mitya

이것이 바로 OP가 한 일입니다. 실제로 약속보다 지연된 패턴을 다시 발명하고 있습니다. 물론 이것이 가능하고 접근 방식이 작동하지만 (초기 OP 코드)이 방법은 허용 된 답변에 설명 된 "투사 안전 이유"로 인한 최선의 방법이 아닙니다.
dhilt

4

그래 넌 할수있어. CustomEvent브라우저 환경에 API를 사용 합니다. 그리고 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>

이 답변이 도움이 되길 바랍니다.


3

우리 솔루션은 클로저를 사용하여 해결 / 거부 기능을 저장하고 약속 자체를 확장하는 기능을 추가로 추가했습니다.

패턴은 다음과 같습니다.

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');

2
대단한 일입니다. 저는 단지 약속을 배우고 있지만 "다른 곳에서"해결할 수 없다는 사실에 계속 당황하고 있습니다. 클로저를 사용하여 구현 세부 사항을 숨기는 것은 좋은 생각입니다 ...하지만 실제로 그것이 당신이 한 일인지 확실하지 않습니다 : "의사"개인 변수가 아닌 변수를 완전히 숨길 수 있는 방법이 있다고 확신 합니다 이는 ... 평균 폐쇄 정말 무엇 인 ... 액세스 할 수 있어야
마이크 설치류

> 클로저는 둘러싸는 범위의 변수에 액세스하여 참조 및 전달할 수있는 코드 블록입니다. var _resolve, _reject; 둘러싸는 범위입니다.
Steven Spungin

그렇습니다, 충분히 공평합니다. 실제로 내 대답은 일을 너무 복잡하게 만드는 것 같습니다. 또한 답을 단순화 할 수 있습니다. 그냥 가면됩니다 promise.resolve_ex = _resolve; promise.reject_ex = _reject;... 여전히 잘 작동합니다.
마이크 설치류

" 약속 자체를 확장하는 기능을 추가하십시오. "-그렇게하지 마십시오. 약속은 결과 값이며이를 해결할 수있는 기능을 제공해서는 안됩니다. 당신은 그 확장 된 것들을 전달하고 싶지 않습니다.
Bergi

2
문제는 범위 밖에서 해결하는 방법이었습니다. 다음은 효과가있는 솔루션이며 프로덕션 환경에서 실제로 필요한 이유가 있습니다. 언급 된 문제를 해결하는 데 공감대가 필요한 이유를 모르겠습니다.
Steven Spungin

2

특정 경우에도 지연 패턴이 누락되었습니다. 언제든지 ES6 Promise 위에 하나를 만들 수 있습니다.

export default class Deferred<T> {
    private _resolve: (value: T) => void = () => {};
    private _reject: (value: T) => void = () => {};

    private _promise: Promise<T> = new Promise<T>((resolve, reject) => {
        this._reject = reject;
        this._resolve = resolve;
    })

    public get promise(): Promise<T> {
        return this._promise;
    }

    public resolve(value: T) {
        this._resolve(value);
    }

    public reject(value: T) {
        this._reject(value);
    }
}

2

이 글에 게시 한 모든 분들께 감사드립니다. 앞에서 설명한 Defer () 객체와 그 위에 구축 된 다른 객체를 포함하는 모듈을 만들었습니다. 이들은 모두 약속과 깔끔한 ​​Promise 콜백 구문을 활용하여 프로그램 내에서 통신 / 이벤트 처리를 구현합니다.

  • 지연 : 원격으로 해결할 수있는 약속 (본체 외부)
  • 지연 : 특정 시간이 지나면 자동으로 해결되는 약속
  • TimeOut : 주어진 시간이 지나면 자동으로 실패하는 약속.
  • 주기 : 약속 구문을 사용하여 이벤트를 관리 할 수있는 재 트리거 가능한 약속
  • 대기열 : 약속 체인을 기반으로 한 실행 대기열입니다.

    rp = require("repeatable-promise")

    https://github.com/CABrouwers/repeatable-promise


1

나는 이것을 위해 작은 라이브러리를 썼다. 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.");

1

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


0

거부를 납치하여 반환하는 함수를 만드는 것은 어떻습니까?

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();

0

나는 그 일을하는 요지를 정리했다 : 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();
  });
  ...
});

0

브라우저 또는 노드에서 먼저 --allow-natives-syntax를 활성화하십시오.

const p = new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

onClick = function () {
    %ResolvePromise(p, value)
}

0

외부에서 약속을 해결하는 또 다른 솔루션

 class Lock {
        #lock;  // Promise to be resolved (on  release)
        release;  // Release lock
        id;  // Id of lock
        constructor(id) {
            this.id = id
            this.#lock = new Promise((resolve) => {
                this.release = () => {
                    if (resolve) {
                        resolve()
                    } else {
                        Promise.resolve()
                    }
                }
            })
        }
        get() { return this.#lock }
    }

용법

let lock = new Lock(... some id ...);
...
lock.get().then(()=>{console.log('resolved/released')})
lock.release()  // Excpected 'resolved/released'
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.