525 Stimmen

Wie kann ich auf Promise-Auflösungsrückrufe außerhalb des Scope des Promise-Constructor-Rückrufs zugreifen?

Normalerweise wird ein Promise konstruiert und wie folgt verwendet:

new Promise((resolve, reject) => {
  const obj = new MyEventEmitter();
  obj.onsuccess = (event) => { resolve(event.result); };
  obj.onerror = (event) => { reject(event.error); };
});

Aber in letzter Zeit habe ich etwas Ähnliches wie das folgende gemacht, um den Resolver außerhalb des Executor-Callbacks flexibler zu handhaben:

let outsideResolve;
let outsideReject;
new Promise((resolve, reject) => {
  outsideResolve = resolve; 
  outsideReject = reject; 
});

Und später:

onClick = function() {
  outsideResolve();
}

Dies funktioniert gut, aber gibt es einen einfacheren Weg, dies zu tun? Wenn nicht, ist dies eine gute Praxis?

10voto

Carsten Hess Punkte 1764

Ich vermisse das Deferred-Muster auch in bestimmten Fällen. Sie können immer eine auf einer ES6 Promise erstellen:

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

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

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

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

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

9voto

jamess Punkte 513

Viele der Antworten hier ähneln dem letzten Beispiel in diesem Artikel. Ich zwischenspeichere mehrere Promises, und die resolve() und reject() Funktionen können einer beliebigen Variable oder Eigenschaft zugewiesen werden. Dadurch kann ich diesen Code etwas kompakter machen:

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

Hier ist ein vereinfachtes Beispiel für die Verwendung dieser Version von defer(), um ein FontFace Lade-Promise mit einem anderen asynchronen Prozess zu kombinieren:

function onDOMContentLoaded(evt) {
    let all = []; // Array von Promises
    glob = {};    // global verwendetes Objekt
    defer(glob);
    all.push(glob.promise);
    // starte asynchronen Prozess mit Rückruf = 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() {} // wird ausgeführt, nachdem alle Promises aufgelöst wurden

Update: 2 Alternativen, falls Sie das Objekt kapseln möchten:

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

und

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

5voto

bgondy Punkte 1048

In naher Zukunft werden wir in der Lage sein, das native Promise.withResolvers() zu verwenden, das jetzt in Stufe 4 ist und bereits in Blink/WebKit-basierten Browsern implementiert ist zum Zeitpunkt des Schreibens.

In der Zwischenzeit verwende ich eine Hilfsfunktion mit der gleichen Signatur:

export default function usePromise() {
  const noop = () => {};

  let localResolve: (value: T | PromiseLike) => void = noop;
  let localReject: (reason?: unknown) => void = noop;

  const promise = new Promise((resolve, reject) => {
    localResolve = resolve;
    localReject = reject;
  });

  return {
    promise,
    resolve: localResolve,
    reject: localReject,
  };
}

// Nutzung
const { promise, resolve, reject } = usePromise();

// Beispiel
const triggerEl = document.querySelector('.trigger');
const someEl = document.querySelector('.dummy-element');

const { promise, resolve } = usePromise();

someEl.addEventListener('transitionend', () => {
  resolve();
});

triggerEl.addEventListener('click', async () => {
  someEl.classList.add('fade-out');

  await promise;

  // Warten bis die Transition endet, bevor das Element entfernt wird
  someEl.remove();
});

Es ist erwähnenswert, dass dies auch ein gültiges Vue3-Komponenten ist.

4voto

Steven Spungin Punkte 22085

Unsere Lösung bestand darin, Closures zu verwenden, um die resolve/reject-Funktionen zu speichern und zusätzlich eine Funktion anzuhängen, um das Versprechen selbst zu erweitern.

Hier ist das Muster:

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

Und so wird es verwendet:

var promise = getPromise();

promise.then(value => {
    console.info('Das Versprechen wurde erfüllt: ' + value);
});

promise.resolve_ex('hallo');  
// oder die Ablehnungsversion 
//promise.reject_ex('auf Wiedersehen');

3voto

Jerem Lachkar Punkte 724

Klassenversion, in Typescript :

export class Deferred {
    public readonly promise: Promise
    private resolveFn!: (value: T | PromiseLike) => void
    private rejectFn!: (reason?: any) => void

    public constructor() {
        this.promise = new Promise((resolve, reject) => {
            this.resolveFn = resolve
            this.rejectFn = reject
        })
    }

    public reject(reason?: any): void {
        this.rejectFn(reason)
    }

    public resolve(param: T): void {
        this.resolveFn(param)
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X