Javascript Promise'ın işlev kapsamının dışında kalmasını sağlayın


280

ES6 Promise kullanıyorum.

Normalde, bir Söz böyle inşa edilir ve kullanılır

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

Ama esneklik uğruna dışarıdaki çözümü almak için aşağıdaki gibi bir şey yapıyorum.

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

Ve sonra

onClick = function(){
    outsideResolve();
}

Bu iyi çalışıyor, ancak bunu yapmanın daha kolay bir yolu var mı? Değilse, bu iyi bir uygulama mı?


2
Başka bir yol olduğunu sanmıyorum. Ben geçti geri arama Promiseiki işlevi "ihracat" izin vermek için senkronize yürütülmesi gerektiği belirtilmektedir .
Felix Kling

1
Bu benim için aynen yazdığınız gibi çalışıyor. Bence bu "kanonik" yol.
Gilad Barner

14
Gelecekte bunu başarmanın resmi bir yolu olması gerektiğini düşünüyorum. Bu özellik bence çok güçlü çünkü diğer bağlamlardan değerleri bekleyebilirsiniz.
Jose

Bu soruna uygun bir çözüm bulduklarında, umarım bunu iç içe vaatler için de işe yarayacaktır, bazıları tekrarlanabilir.
Arthur Tarasov

Ben Promise API her zaman döndürme değerleri olarak kullanmak ve asla erişebilirsiniz veya çağrı nesneler olarak kullanmak için "önermek" düşünüyorum. Başka bir deyişle, onları erişebileceğimiz nesneler veya arayabileceğimiz işlevler veya bir değişkenle başvurabileceğimiz veya parametre olarak geçebileceğimiz bir şey yerine döndürme değerleri olarak ele almaya zorlar. Sonunda sorunuzu olduğu gibi dışarıdan çözmeniz gerekiyor ... Bununla birlikte, bunu yapmanın resmi bir yolu olması gerektiğini düşünüyorum ... ve Ertelenmiş benim için sadece bir çözüm gibi görünüyor.
kanserbero

Yanıtlar:


93

Hayır, bunu yapmanın başka bir yolu yok - söyleyebileceğim tek şey bu kullanım durumunun çok yaygın olmadığıdır. Felix'in yorumda söylediği gibi - yaptığınız şey sürekli olarak çalışacaktır.

Söz vericinin bu şekilde davranmasının nedeninin atma güvenliği olduğunu belirtmek gerekir - kodunuz söz vericinin içinde çalışırken beklemediğiniz bir istisna gerçekleşirse, bu bir reddetmeye dönüşecektir, bu atma güvenliği biçimi - atılan hataları Reddetme önemlidir ve öngörülebilir kodun korunmasına yardımcı olur.

Bu atış güvenliği nedeni için, söz veren kurucu ertelemeler (bu, ne yaptığınıza izin veren alternatif bir söz verme yolu) olarak seçildi - en iyi uygulamalarda olduğu gibi - öğeyi geçip söz vericiyi kullanacağım:

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

Ne zaman - bu nedenle can işlevleri ihracat üzerinde söz yapıcı kullanmak - Bunu kullanırım önerilir. Her ikisinden de kaçınabileceğiniz zaman - her ikisinden de ve zincirden kaçının.

Söz veren yapıcıyı asla böyle şeyler için kullanmamalısınız if(condition), ilk örnek şu şekilde yazılabilir:

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

2
Merhaba Benjamin! Şu anda ne zaman tamamlanacağını bilmiyorsak şu anda nefis söz şekeri almanın daha iyi bir yolu yok mu? Bir çeşit asenkron bekleme / bildirim şekli gibi mi? Örneğin, "mağaza" gibi ve daha sonra bir Promisezincir çağırmak ? Örneğin, benim durumumda, belirli bir istemci yanıtı (istemcinin durumu başarıyla güncelleştirildiğinden emin olmak için bir SYN-ACK-tür el sallamak) için bekleyen bir sunucudayım.
Domi

1
@Domi, q-bağlantısı ve RxJS'yi kontrol eder.
Benjamin Gruenbaum

2
Getirme API'sını kullanarak aynı şeyi nasıl yapabilirim?
Vinod Sobale

95
Yaygın değil? Neredeyse her projeye ihtiyacım var.
Tomáš Zato - Monica'yı eski durumuna döndür

1
Usecase'e gelince, bir olay tetiklendikten ve başka bir şey olduktan sonra bir şeyler yapmanız gerekir. Olayı bir vaat haline getirmek ve başka bir vaatle birleştirmek istiyorsunuz. Bana genel bir sorun gibi geliyor.
Gherman

130

basit:

var promiseResolve, promiseReject;

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

promiseResolve();

2
@ruX, Kabul edilen cevaptan bahsedildiği gibi - bu şekilde tasarlanmıştı. Mesele şu ki, bir istisna atılırsa söz vericisi tarafından yakalanır. Bu cevap (benimki gibi), kodun istediği her şey için bir istisna atma tuzağına sahiptir promiseResolve(). Bir sözün anlambilimi, daima bir değer döndürmesidir. Ayrıca bu OP işlevsel olarak aynıdır, bunun yeniden kullanılabilir bir şekilde çözdüğü problemi anlamıyorum.
Jon Jaques

4
@JonJaques Söylediklerinin doğru olup olmadığından emin değilim. Çağıran kod promiseResolve()bir istisna atmaz. Yapıcıda bir tanımlayabilirsiniz .catchve hangi kodu çağırırsa çağırsın, yapıcı .catchçağrılır. İşte bunun nasıl çalıştığını gösteren jsbin: jsbin.com/yicerewivo/edit?js,console
carter

Evet, yakalandı çünkü etrafına başka bir söz kurucu sardın - Tam olarak yapmaya çalıştığım nokta. Ancak, yapıcı dışında (ertelenmiş nesne olarak da bilinir) çağırmaya çalışan başka bir kodunuz olduğunu varsayalım
Jon Jaques

8
Kötü bir tasarım olduğundan bile emin değilim. Sözün dışında atılan bir hatanın sözün içinde yakalanması gerekmez. Tasarımcı gerçekten beklerse , belki de yanlış anlama veya kötü anlama örneğidir. hata içinde yakalanmak.
KalEl

3
Bu kesin yapı soruda zaten belirtilmiştir. Hatta okudun mu?
Cedric Reichenbach

103

Burada partiye biraz geç, ama bunu yapmanın başka bir yolu Ertelenmiş bir nesne kullanmak olacaktır . Esasen aynı miktarda kazan plakasına sahipsiniz, ancak bunları etrafta geçirmek ve muhtemelen tanımlarının dışında çözmek istiyorsanız kullanışlıdır.

Saf Uygulama:

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 Sürümü:

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
Sözcüksel kapsam belirlemeye dikkat edin.
Florrie

1
resolve|rejectSözcüksel olarak veya yoluyla atanma konusunda pratik bir fark yoktur bind. Bu sadece 1.0 (ish) beri etrafında olan jQuery Ertelenmiş nesnenin basit bir uygulamasıdır . Tam bir söz gibi çalışır, ancak atış güvenliği yoktur. Bu sorunun asıl amacı, vaatler yaratırken birkaç satır kodun nasıl kaydedileceği idi.
Jon Jaques

1
Ertelenmiş kullanmak bunu yapmak için her zamanki gibi, neden daha yüksek değil hiçbir fikrim yok
BlueRaja - Danny Pflughoeft

1
Mükemmel cevap! JQuery sunduğu ertelenmiş işlevsellik arıyordu.
Anshul Koka

2
Is Deferredkullanımdan kaldırıldı?
Pacerier

19

Çerçevem ​​için 2015 yılında bulduğum bir çözüm. Bu tür sözleri Görev olarak adlandırdım

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
Teşekkürler, bu işe yaradı. Ancak işleyici nedir? Çalışması için onu çıkarmak zorunda kaldım.
Sahid

16

@JonJaques cevabını beğendim ama bir adım daha ileri almak istedim.

Eğer nesneyi bağlar thenve catchsonra Deferred, o zaman tamamen PromiseAPI uygular ve awaitonu söz ve o ve benzeri olarak ele alabilirsiniz.

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

Bir yardımcı yöntem bu ek yükü hafifletir ve size aynı jQuery hissini verir.

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

Kullanım olurdu

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

JQuery benzer

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

Her ne kadar, bir kullanım durumunda bu basit, yerel sözdizimi iyidir

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

8

Ben "düz söz" dediğim şeyi oluşturmak için bir yardımcı işlevi kullanıyorum -

function flatPromise() {

    let resolve, reject;

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

    return { promise, resolve, reject };
}

Ve ben böyle kullanıyorum -

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;

}

Tüm çalışma örneğine bakın -

Düzenleme: Flat-promise adlı bir NPM paketi oluşturdum ve kod GitHub'da da mevcut .


7

Sözü bir sınıfa sarabilirsiniz.

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

Buradaki cevapların çoğu bu makaledeki son örneğe benzer . Birden fazla Promise önbelleğe alıyorum ve resolve()ve reject()fonksiyonları herhangi bir değişkene veya özelliğe atanabilir. Sonuç olarak bu kodu biraz daha kompakt hale getirebiliyorum:

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

defer()Bir FontFaceYük Vaatini başka bir zaman uyumsuz işlemle birleştirmek için bu sürümünün kullanılmasının basitleştirilmiş bir örneği :

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 

Güncelleme: Nesneyi kapsüllemek istediğinizde 2 alternatif:

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

ve

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

Bu örnekleri zaman uyumsuz bir işlevde kullanıyorsanız, çözülmüş const result = await deferred.promise;
vaatün

6

Kabul edilen cevap yanlış. Kapsam ve referansları kullanmak oldukça kolaydır, ancak Promise saflarını öfkelendirebilir:

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

Söz verildiği zaman temelde çözüm işlevine referans alıyoruz ve bunu harici olarak ayarlanabilmesi için geri döndürüyoruz.

Bir saniye içinde konsol çıktı verecektir:

> foo

Bence bu en iyi yaklaşım. Tek şey, kodun biraz daha az ayrıntılı olması olabilir.
pie6k

Güzel! Zekice fikir. +50 yapabilseydim.
Mitya

OP'nin yaptığı da buydu. Aslında, Vaatler üzerinde Ertelenmiş modeli yeniden icat ediyorsunuz, elbette bu mümkündür ve yaklaşımınız işe yarar (ilk OP kodu olarak), ancak bu, kabul edilen cevapta açıklanan "güvenlik nedeni" nedeniyle en iyi uygulama değildir.
dhilt

4

Evet yapabilirsin. CustomEventTarayıcı ortamı için API kullanarak . Ve node.js ortamlarında bir olay yayıcı proje kullanmak. Söz konusu pasaj tarayıcı ortamı için olduğundan, bunun için çalışan bir örnek.

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>

Umarım bu cevap faydalıdır!


3

Çözümümüz, çözme / reddetme işlevlerini saklamak ve ek olarak sözün kendisini genişletmek için bir işlev eklemek için kapaklar kullanmaktı.

İşte desen:

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

Ve kullanarak:

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
Harika ... Ben sadece Promises öğreniyorum ama onları "başka bir yerde" çözemeyeceğin gerçeğiyle sürekli şaşkına döndüm. Uygulama ayrıntılarını gizlemek için bir kapatma kullanmak harika bir fikir ... ama aslında yaptığınız şeyden emin değilim: "sözde" özel değişkenlere sahip olmak yerine değişkenleri tamamen gizlemenin bir yolu olduğundan eminim Bu erişilemez olmalı ... bu gerçekten kapanışların ne anlama geldiği ...
mike rodent

> Kapatma, çevreleme kapsamının değişkenlerine erişim ile başvurulan (ve etrafından geçirilebilen) bir kod bloğudur. var _resolve, _reject; çevreleme kapsamıdır.
Steven Spungin

evet, yeterince adil. Aslında bana öyle geliyor ki cevabım çok karmaşık şeyler ve ayrıca cevabınız basitleştirilebilir: sadece gitmeniz gerekiyor promise.resolve_ex = _resolve; promise.reject_ex = _reject;... hala iyi çalışıyor.
mike rodent

" sözün kendisini genişletmek için bir işlev ekleyin. " - bunu yapma. Vaatler sonuç değerleridir, bunları çözme yeteneği sağlamamalıdır. Bu uzatılmış olanları geçmek istemezsiniz.
Bergi

2
Soru, kapsam dışında nasıl çözüleceğiydi. İşte işe yarayan bir çözüm ve üretimimizde aslında bunu yapmak için gerekli bir nedenimiz var. Belirtilen sorunun çözülmesinin neden bir aşağı oyu hak ettiğini anlamıyorum.
Steven Spungin

2

Bazı durumlarda ertelenmiş kalıbı kendim de eksik buluyorum. Her zaman bir ES6 Sözünün üstünde bir tane oluşturabilirsiniz:

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

Bu konuyu yayınlayan herkese teşekkürler. Daha önce açıklanan Defer () nesnesini ve üzerine inşa edilmiş birkaç nesneyi içeren bir modül oluşturdum. Hepsi bir programda iletişim / olay işlemeyi uygulamak için Promises ve temiz Promise geri arama sözdizimini kullanır.

  • Ertele: Uzaktan çözülebilecek söz (bedeni dışında)
  • Gecikme: Belirli bir süre sonra otomatik olarak çözülen söz
  • TimeOut: Belirli bir süre sonra otomatik olarak başarısız olan söz.
  • Döngü: Olayları Promise sözdizimi ile yönetmek için yeniden tetiklenebilir vaat
  • Kuyruk: Promise zincirine dayalı yürütme kuyruğu.

    rp = require("repeatable-promise")

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


1

Bunun için küçük bir kitap yazdım. https://www.npmjs.com/package/@inf3rno/promise.exposed

Diğerleri yazdı fabrika yöntemi yaklaşımı kullanılır, ama ben overrode then, catch, finallyyöntemleri de, bu yüzden sıra bu orjinal sözünü çözebilirsiniz.

Dışardan icracı olmadan sözü çözme:

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

İcracı setiyle yarışmak Dışarıdan zaman aşımı:

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

Genel ad alanını kirletmek istemiyorsanız çakışma yok modu vardır:

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

1

Bunun manual-promiseyerine bir damla işlevi gören bir kütüphane yaptım Promise. Buradaki diğer cevapların hiçbiri Promisevekil veya sargı kullandıkları için değiştirmelerde düşüş olarak çalışmaz .

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

Reddetmeyi kaçırmak ve geri vermek için bir işlev oluşturmaya ne dersiniz?

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

Bu işi yapan bir özeti bir araya getirdim: https://gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13

nasıl kullanacağınız aşağıda açıklanmıştır:

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

önce tarayıcıda veya düğümde --allow-natives-sözdizimini etkinleştir

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

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

0

Promise'i dışarıdan çözmek için başka bir çözüm

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

kullanım

let lock = new Lock(... some id ...);
...
lock.get().then(()=>{console.log('resolved/released')})
lock.release()  // Excpected 'resolved/released'
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.