関数スコープ外のJavascript Promiseを解決する


279

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

これは正常に動作しますが、これを行う簡単な方法はありますか?そうでない場合、これは良い習慣ですか?


2
別の方法があるとは思いません。渡されたコールバックはPromise、2つの関数を「エクスポート」できるように同期的に実行する必要があると指定されていると思います。
Felix Kling 2014年

1
これは、あなたが書いたとおりに機能します。ですから、私に関する限り、これは「標準的な」方法です。
Gilad Barner

14
これを実現する正式な方法が将来あるべきだと思います。他のコンテキストからの値を待つことができるので、この機能は私の意見では非常に強力です。
ホセ

彼らがこの問題の適切な解決策を考え出すときはいつでも、ネストされたプロミスにもそれが機能するように願っています。
Arthur Tarasov

Promise APIは常にそれらを戻り値として使用し、アクセスまたは呼び出し可能なオブジェクトとして使用しないことを「推奨」すると思います。つまり、アクセスできるオブジェクトや呼び出し可能な関数や、変数で参照したり、パラメータとして渡したりできるものではなく、戻り値としてそれらを処理するように強制します。他のオブジェクトと同じようにpromiseの使用を開始すると、おそらく結局、あなたの質問のようにそれを外部から解決する必要があります...とは言っても、これを行う正式な方法があるはずだと思います...そしてDeferredは私にとっては回避策のようです。
Cancerbero

回答:


92

いいえ、これを行う方法は他にありません。唯一言えるのは、この使用例はあまり一般的ではないということです。コメントでフェリックスが言ったように-あなたがすることは一貫して機能します。

プロミスコンストラクターがこのように動作する理由はスローセーフティであることは言及に値します-プロミスコンストラクター内でコードが実行されているときに予期しない例外が発生すると、拒否に変わります。この形式のスローセーフティ-スローされたエラーをに変換します拒否は重要であり、予測可能なコードを維持するのに役立ちます。

このスローの安全性の理由から、遅延コンストラクターよりもpromiseコンストラクターが選択されました(これは、実行していることを許可する代替のpromise構築方法です)。ベストプラクティスと同様に、要素を渡し、代わりにpromiseコンストラクターを使用します。

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

このため、関数のエクスポートよりもpromiseコンストラクターを使用できる場合はいつでも、使用することをお勧めします。両方を回避できる場合は常に、両方とチェーンを回避してください。

のようなものにはpromiseコンストラクタを決して使用しないでください。if(condition)最初の例は次のように書くことができます。

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

2
こんにちは、ベンジャミン!約束がいつ履行されるかわからない場合、おいしい約束の砂糖を手に入れるためのより良い方法は現在ありませんか?非同期の待機/通知パターンのようなものですか?たとえば「ストア」のように、後でPromiseチェーンを呼び出しますか?たとえば、私の特定のケースでは、私はサーバー上にいて、特定のクライアントの応答(クライアントが状態を正常に更新したことを確認するためのSYN-ACK-kindaハンドシェイク)を待っています。
ドミ2015年

1
@Domiはq-connectionとRxJSをチェックしてください。
Benjamin Gruenbaum 2015年

2
フェッチAPIを使用して同じことをするにはどうすればよいですか?
Vinod Sobale

95
一般的ではありませんか?ほとんどすべてのプロジェクトで必要になります。
トマーシュZato -復活モニカ

1
ユースケースについては、イベントがトリガーされて何かが起こった後に何かをする必要があると考えてください。イベントを約束に変換し、それを別の約束と統合したい。私には一般的な問題のようです。
ガーマン、2017年

130

シンプル:

var promiseResolve, promiseReject;

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

promiseResolve();

2
@ruX、受け入れられた答えが言及するように-それは意図的にこのように設計されました。ポイントは、例外がスローされた場合、それがpromiseコンストラクターによってキャッチされることです。この答え(そして私のもの)は、コード呼び出しが何であっても例外をスローする可能性があるという落とし穴がありますpromiseResolve()。promiseのセマンティクスは、常に値を返すことです。また、これはOPの投稿と機能的に同じですが、これが再利用可能な方法で解決している問題はわかりません。
Jon Jaques

4
@JonJaquesあなたの言うことが本当かどうかわかりません。を呼び出すコードpromiseResolve()は例外をスローしません。.catchコンストラクターでを定義できます。どのコードがそれを呼び出しても、コンストラクター.catchが呼び出されます。これがどのように機能するかを示すjsbinは次のとおりです。jsbin.com
カーター

ええ、あなたはそれを別のプロミスコンストラクターで囲んだために見つかりました。ただし、コンストラクタの外部でresolve()を呼び出そうとしている他のコード(別名Deferredオブジェクト)があるとしましょう...例外がスローされ、キャッチされない可能性がありますjsbin.com/cokiqiwapo/1/edit?js,console
Jon Jaques

8
それが悪いデザインかどうかさえわかりません。promiseの外部でスローされたエラーは、promise内でキャッチされるものではありません。設計者が実際にエラーが捕捉されることを期待している場合、それはおそらく誤解または悪い理解の例です。
KalEl 2017年

3
この正確な構成は質問ですでに述べられています。あなたもそれを読みましたか?
Cedric Reichenbach 2017

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、2016

1
resolve|reject字句的に割り当てられるか、を通じて割り当てられるかには、実際的な違いはありませんbind。これは、1.0(ish)以降のjQuery Deferredオブジェクトの単純な実装です。スローの安全性がないことを除いて、それは約束とまったく同じように機能します。この質問の要点は、プロミスを作成するときに数行のコードを保存する方法でした。
Jon Jaques

1
ある繰延通常の方法を使用すると、これを行うには、これは高くない理由を、私は考えている
BlueRaja -ダニーPflughoeft

1
正解です。jQueryが提供する遅延機能を探していました。
Anshul Koka

2
されるDeferred非推奨?
Pacerier 2017年

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
ありがとう、これでうまくいきました。しかし、ハンドラーとは何ですか?私はそれを機能させるためにそれを取り除く必要がありました。
サヒド

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

クラスでPromiseをラップできます。

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

ここでの回答の多くは、この記事の最後の例に似ています。私は複数のPromiseをキャッシュしresolve()ていreject()ますが、および関数は任意の変数またはプロパティに割り当てることができます。その結果、このコードをわずかにコンパクトにすることができます。

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

このバージョンのdefer()を使用してFontFaceload Promiseを別の非同期プロセスと組み合わせる簡単な例を次に示します。

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

:あなたは非同期機能でこれらの例を使用している場合は、解決の約束の値を使用したい場合、あなたは、約束のプロパティを参照する必要がありますconst result = await deferred.promise;
b00tを

6

受け入れられた答えは間違っています。スコープと参照の使用は非常に簡単ですが、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);

基本的には、promiseが作成されたときにresolve関数への参照を取得し、それを外部に設定できるように返します。

1秒後にコンソールが出力します:

> foo

これが最善のアプローチだと思います。唯一のことは、コードが少し冗長になる可能性があるということです。
pie6k

いいね!賢いアイデア。できれば+50。
Mitya

これはOPが行ったことと同じです。実際、PromiseでDeferredパターンを再発明していますが、もちろんこれは可能であり、アプローチは(最初の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
すばらしい...私はPromiseを学んでいるだけですが、「どこか別の場所」でそれらを解決できないように見えるという事実に一貫して困惑しています。クロージャーを使用して実装の詳細を非表示にすることは素晴らしいアイデアです...しかし、実際にはそれがあなたがやったことかわかりません:「疑似」プライベート変数を持っているのではなく、変数を完全に隠す方法があると確信ていますこれはアクセスできないはずです...これは実際にクロージャが意味することです...
マイク齧歯動物

>クロージャーは、囲んでいるスコープの変数へのアクセスで参照(および引き渡し)できるコードのブロックです。var _resolve、_reject; 囲んでいるスコープです。
Steven Spungin 2017

うん、十分に公正です。実際、私の答えは複雑すぎるものであり、さらにあなたの答えは単純化できると私には思われますpromise.resolve_ex = _resolve; promise.reject_ex = _reject;
マイクげっ歯類2017

promise自体を拡張する関数をアタッチします。」-しないでください。約束は結果の値であり、それらを解決する機能を提供するべきではありません。あなたはそれらの拡張されたものを渡したくありません。
Bergi

2
問題は、それを範囲外で解決する方法でした。これが機能する解決策であり、私たちの生産では実際にそれを行うために必要な理由がありました。述べられた問題を解決することが反対票に値する理由はわかりません。
Steven Spungin 2017

2

場合によっては、Deferredパターンも見逃してしまいます。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ときちんとしたPromiseコールバック構文を利用して、プログラム内で通信/イベント処理を実装します。

  • 延期:解決できる約束はリモートで(その本体の外で)失敗しました
  • 遅延:一定時間後に自動的に解決される約束
  • TimeOut:一定時間後に自動的に失敗する約束。
  • サイクル:Promise構文でイベントを管理するための再トリガー可能なpromise
  • キュー:Promiseチェーンに基づく実行キュー。

    rp = require("repeatable-promise")

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


1

私はこれのために小さなlibを書きました。https://www.npmjs.com/package/@inf3rno/promise.exposed

私は他の人が書いたファクトリメソッドのアプローチを使用し、私はオーバーライドthencatchfinally方法はあまりにも、あなたがだけでなく、それらによるオリジナルの約束を解決することができます。

外部からのエグゼキュータなしでPromiseを解決する:

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.