web-dev-qa-db-ja.com

Javascript Promiseを関数の範囲外で解決する

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

これはうまくいきますが、もっと簡単な方法はありますか?そうでなければ、これは良い習慣ですか?

217
Morio

いいえ、これを実行する他の方法はありません - 私が言えるのは、このユースケースはあまり一般的ではないということです。 Felixがコメントで言ったように - あなたがすることは一貫してうまくいくでしょう。

Promiseコンストラクタがこのように振る舞う理由は安全性を投げることです - あなたのコードがpromiseコンストラクタの中で実行されている間にあなたが予想しなかった例外が起こるなら拒絶に変わるでしょう。拒否は重要であり、予測可能なコードを維持するのに役立ちます。

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

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

このため - あなたがあなたが関数をエクスポートするよりもpromiseコンストラクタを使うことができる時はいつでも - 私はあなたがそれを使うことを勧めます。両方を避けることができるときはいつでも - 両方とチェーンを避けてください。

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

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

シンプル:

var promiseResolve, promiseReject;

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

promiseResolve();
101
carter

ここでパーティーに少し遅れますが、それを行う別の方法は 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
})
76
Jon Jaques

私のフレームワークのために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
16
Maxmaxmaximus

私は@ JohnJaquesの回答が好きでしたが、それをさらに一歩進めたかったのです。

thencatch、そしてDeferredオブジェクトをバインドすると、それはPromise 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();
13
Rico Kahler

ヘルパーメソッドはこの余分なオーバーヘッドを軽減し、あなたに同じ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
    });
});
9
Cory Danielson

私は "フラットプロミス"と呼ぶものを作成するためにヘルパー関数を使用しています -

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

})();

編集:私は flat-promise と呼ばれるNPMパッケージを作成しました - そしてコードも入手可能です GitHub上

7
Arik

約束をクラスで包むことができます。

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.
3
Hinrich

はい、できます。ブラウザ環境にCustomEvent APIを使用する。そして、node.js環境でのEvent Emitterプロジェクトの使用問題のスニペットはブラウザ環境用であるため、ここでは同じ例を示します。

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
Steven Spungin

ここでの答えの多くは、 この記事 の最後の例に似ています。私は複数のPromiseをキャッシュしています、そしてresolve()reject()関数はどんな変数またはプロパティにも割り当てることができます。その結果、このコードをもう少しコンパクトにすることができます。

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

これは、このバージョンのdefer()を使用してFontFace load 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 
0
jamess

拒否をハイジャックして返すための関数を作成するのはどうですか。

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
nikksan

最初にブラウザまたはノードで--allow-natives-syntaxを有効にします

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

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

0
Equitable

私はその仕事をする要旨をまとめました。 https://Gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef1

使い方は次のとおりです。

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
thiagoh

Promiseの代わりにドロップインとして機能するmanual-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
jeohd

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

私は他の人が書いたファクトリメソッドアプローチを使用しましたが、私はthencatchfinallyメソッドもオーバーライドしたので、それらによっても元の約束を解決することができます。

外部からexecutorを使わずにPromiseを解決する:

const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");

外部からexecutorの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.");
0
inf3rno

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

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

コンソールは1秒で出力します:

> foo
0
Ali