web-dev-qa-db-fra.com

Résoudre la promesse Javascript en dehors de la portée de la fonction

J'ai utilisé ES6 Promise.

D'habitude, une promesse est construite et utilisée comme ceci

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

Mais je fais quelque chose comme ci-dessous pour prendre la résolution à l'extérieur par souci de flexibilité.

var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) { 
    outsideResolve = resolve; 
    outsideReject = reject; 
});

Et ensuite

onClick = function(){
    outsideResolve();
}

Cela fonctionne bien, mais existe-t-il un moyen plus simple de le faire? Si non, est-ce une bonne pratique? 

190
Morio

Non, il n'y a pas d'autre moyen de faire cela - la seule chose que je peux dire, c'est que ce cas d'utilisation n'est pas très courant. Comme Felix l'a dit dans son commentaire, ce que vous ferez fonctionnera de manière constante.

Il convient de mentionner que la raison pour laquelle le constructeur de promesse se comporte de cette manière est la sécurité du jet. Si une exception que vous n'aviez pas anticipée se produit lorsque votre code est exécuté dans le constructeur de la promesse, elle se transformera en rejet, cette forme de sécurité du jet - convertir les erreurs renvoyées en les rejets sont importants et permettent de maintenir un code prévisible.

Pour cette raison de sécurité, le constructeur de promesse a été choisi plutôt que différé (une méthode de construction de promesse alternative autorisant ce que vous faites). Pour ce qui est des meilleures pratiques, je passerais l'élément et utiliserais plutôt le constructeur de promesse:

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

Pour cette raison, chaque fois que vous pouvez utiliser le constructeur de promesse au lieu d'exporter les fonctions - je vous recommande de l'utiliser. Chaque fois que vous pouvez éviter les deux, évitez les deux.

Notez que vous ne devriez jamais utiliser le constructeur de promesse pour des choses comme if(condition), le premier exemple pourrait être écrit comme suit:

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

simple:

var promiseResolve, promiseReject;

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

promiseResolve();
88
carter

Un peu tard pour le parti ici, mais une autre façon de le faire serait d’utiliser un objet différé . Vous avez essentiellement la même quantité de passe-partout, mais c'est pratique si vous voulez les transmettre et éventuellement résoudre en dehors de leur définition.

Mise en oeuvre naïve:

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

Version 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
})
72
Jon Jaques

Une solution que j'ai proposée en 2015 pour mon framework. J'ai appelé ce type de promesses Task

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
14
Maxmaxmaximus

J'ai aimé @JonJaques répondre mais je voulais aller plus loin.

Si vous liez then et catch à l'objet Deferred, il implémente pleinement l'API Promise et vous pouvez le traiter comme une promesse et await il et ainsi de suite.

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

11
Rico Kahler

Une méthode d'assistance réduirait ces frais supplémentaires et vous donnerait la même sensation jQuery.

function Deferred() {
    let resolve;
    let reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}

L'utilisation serait

const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
    confirm: resolve,
    cancel: reject
});
return promise;

Qui est similaire à jQuery

const dfd = $.Deferred();
displayConfirmationDialog({
    confirm: dfd.resolve,
    cancel: dfd.reject
});
return dfd.promise();

Bien que, dans un cas d'utilisation, cette syntaxe simple et native convient parfaitement

return new Promise((resolve, reject) => {
    displayConfirmationDialog({
        confirm: resolve,
        cancel: reject
    });
});
8
Cory Danielson

J'utilise une fonction d'aide pour créer ce que j'appelle une "promesse sans lendemain" -

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });

    return { promise, resolve, reject };
}

Et je l'utilise comme si -

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;

}

Voir exemple de travail complet -

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

})();

Edit: J'ai créé un package NPM appelé flat-promise et le code est également disponible sur GitHub .

5
Arik

Oui, vous pouvez. En utilisant l'API CustomEvent pour l'environnement de navigateur. Et en utilisant un projet émetteur d'événements dans les environnements node.js. Étant donné que l'extrait de code de la question concerne l'environnement du navigateur, voici un exemple concret.

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>

J'espère que cette réponse est utile!

3

Notre solution consistait à utiliser des fermetures pour stocker les fonctions de résolution/rejet et, en outre, associer une fonction pour étendre la promesse elle-même. 

Voici le motif:

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

Et en l'utilisant:

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

La plupart des réponses ici sont similaires au dernier exemple de cet article . Je mets en cache plusieurs promesses et les fonctions resolve() et reject() peuvent être affectées à toute variable ou propriété. En conséquence, je peux rendre ce code légèrement plus compact:

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

Voici un exemple simplifié d'utilisation de cette version de defer() pour associer une promesse de chargement FontFace à un autre processus asynchrone:

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 

Mise à jour: 2 alternatives au cas où vous souhaitiez encapsuler l'objet:

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

et

class Deferred {
    constructor() {
        this.promise = new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject  = reject;
        });
    }
}
let deferred = new Deferred();
1
jamess

Vous pouvez envelopper la promesse dans une classe. L'exemple utilise le typage Flow, mais peut être utilisé sans bien sûr:

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

Pourquoi ne pas créer une fonction pour pirater le rejet et la renvoyer?

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

activez d'abord --allow-natives-syntax sur le navigateur ou le noeud

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

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

0
Equitable

J'ai créé un Gist qui fait ce travail: https://Gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13

voici comment vous devriez l'utiliser:

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

J'ai créé une bibliothèque appelée manual-promise qui remplacera Promise. Aucune des autres réponses ici ne fonctionnera comme une substitution de remplacement pour Promise, car ils utilisent des mandataires ou des enveloppes.

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

J'ai écrit une petite bibliothèque pour cela. https://www.npmjs.com/package/@inf3rno/promise.exposed

J’ai utilisé la méthode d’usine utilisée par d’autres personnes, mais j’ai aussi écrasé les méthodes then, catch, finally afin que vous puissiez également résoudre la promesse initiale.

Résolution de Promise sans exécuteur extérieur:

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

Course avec setTimeout de l'exécuteur depuis l'extérieur:

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

Il y a un mode sans conflit si vous ne voulez pas polluer l'espace de nom global:

const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");
0
inf3rno

La réponse acceptée est fausse. C'est assez facile d'utiliser la portée et les références, bien que cela puisse rendre Promise puristes en colère:

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

Nous saisissons essentiellement la référence à la fonction de résolution lorsque la promesse est créée, et nous la renvoyons afin qu’elle puisse être définie en externe.

Dans une seconde la console affichera:

> foo
0
Ali