webentwicklung-frage-antwort-db.com.de

Synchrone Versprechenauflösung (Bluebird vs. jQuery)

Ich habe eine kleine Bibliothek für den Dynamics CRM REST/ODATA Webservice (CrmRestKit) entwickelt. Die lib hängt von jQuery ab und verwendet das Versprechungsmuster, respektive das Versprechungsmuster von jQuery. 

Jetzt portiere ich diese lib gerne nach bluebird und entferne die jQuery-Abhängigkeit. Ich bin jedoch mit einem Problem konfrontiert, da Bluebird die synchrone Auflösung von Versprechungsobjekten nicht unterstützt. 

Einige Kontextinformationen:  

Die API des CrmRestKit enthält einen optionalen Parameter, der definiert, ob der Webdienstaufruf im Synchronisationsmodus oder im Asynchronmodus ausgeführt werden soll:

CrmRestKit.Create( 'Account', { Name: "foobar" }, false ).then( function ( data ) {
   ....
} );

Wenn Sie "true" übergeben oder den letzten Parameter weglassen, erstellt die Methode den Datensatz synchron. Modus. 

Manchmal ist es erforderlich, eine Operation im Synchronisationsmodus auszuführen, z. B. können Sie JavaScript-Code für Dynamics CRM schreiben, der für das Sicherungsereignis eines Formulars verwendet wird. In diesem Event-Handler müssen Sie zur Validierung eine Synchronisationsoperation ausführen ( Überprüfen Sie beispielsweise, ob eine bestimmte Anzahl von untergeordneten Datensätzen vorhanden ist. Falls die richtige Anzahl von Datensätzen vorhanden ist, brechen Sie den Speichervorgang ab und zeigen Sie eine Fehlermeldung an. 

Mein Problem ist nun folgendes: bluebird unterstützt die Auflösung im Sync-Modus nicht. Wenn ich zum Beispiel folgendes mache, wird der "then" -Handler auf asynchrone Weise aufgerufen:

function print( text ){

    console.log( 'print -> %s', text );

    return text;
}

///
/// 'Promise.cast' cast the given value to a trusted promise. 
///
function getSomeTextSimpleCast( opt_text ){

    var text = opt_text || 'Some fancy text-value';

    return Promise.cast( text );
}

getSomeTextSimpleCast('first').then(print);
print('second');

Die Ausgabe ist die folgende:

print -> second
print -> first

Ich würde erwarten, dass das "zweite" nach dem "ersten" erscheint, weil das Versprechen bereits mit einem Wert gelöst wurde. Also würde ich davon ausgehen, dass ein then-event-handler sofort aufgerufen wird wenn er auf ein bereits aufgelöstes promise-object angewendet wird .

Wenn ich dasselbe mit jQuery mache (dann mit einem bereits aufgelösten Versprechen verwenden), habe ich mein erwartetes Ergebnis:

function jQueryResolved( opt_text ){

    var text = opt_text || 'jQuery-Test Value',
    dfd =  new $.Deferred();

    dfd.resolve(text);

        // return an already resolved promise
    return dfd.promise();
}

jQueryResolved('third').then(print);
print('fourth');

Dies erzeugt die folgende Ausgabe:

print -> third
print -> fourth

Gibt es eine Möglichkeit, Bluebird auf die gleiche Weise arbeiten zu lassen? 

Update: Der bereitgestellte Code dient nur zur Veranschaulichung des Problems. Die Idee der lib ist: Unabhängig vom Ausführungsmodus (sync, async) wird der Aufrufer immer ein Versprechungsobjekt behandeln. 

Zu "... dem Benutzer fragen ... scheint keinen Sinn zu machen": Wenn Sie die beiden Methoden "CreateAsync" und "CreateSync" angeben, kann der Benutzer auch entscheiden, wie die Operation ausgeführt wird. 

Bei der aktuellen Implementierung ist das Standardverhalten (letzter Parameter ist optional) eine asynchrone Ausführung. Wenn 99% des Codes ein Promise-Objekt erfordern, wird der optionale Parameter nur für die 1% -Fälle verwendet, in denen Sie lediglich eine Synchronisationsausführung benötigen. Außerdem habe ich mich für lib entwickelt und benutze in 99,9999% der Fälle den Async-Modus, aber ich dachte, es wäre schön, wenn Sie die Möglichkeit haben, die Sync-Straße nach Belieben zu wählen. 

Aber ich denke, ich habe den Punkt verstanden, dass eine Synchronisationsmethode den Wert einfach zurückgeben sollte. Für die nächste Version (3.0) werde ich "CreateSync" und "CreateAsync" implementieren. 

Danke für deinen Beitrag.

Update-2 Meine Absicht für den optionalen Parameter bestand darin, ein konsistentes Verhalten zu gewährleisten UND Logikfehler zu vermeiden. Nehmen Sie an, Sie als Konsument meiner Methode "GetCurrentUserRoles", die lib verwendet. Die Methode gibt also immer ein Versprechen zurück, dh Sie müssen die "then" -Methode verwenden, um Code auszuführen, der vom Ergebnis abhängig ist. Wenn also ein solcher Code geschrieben wird, stimme ich zu, dass es völlig falsch ist:

var currentUserRoels = null;

GetCurrentUserRoles().then(function(roles){

    currentUserRoels = roles;
});

if( currentUserRoels.indexOf('foobar') === -1 ){

    // ...
}

Ich stimme zu, dass dieser Code beschädigt wird, wenn die Methode "GetCurrentUserRoles" von Synchronisierung zu Asynchron wechselt. 

Aber ich verstehe, dass dies kein gutes Design ist, denn der Verbraucher sollte sich jetzt mit einer asynchronen Methode beschäftigen. 

12
thuld

Kurze Version: Ich verstehe, warum Sie das wollen, aber die Antwort ist nein.

Ich denke, die zugrunde liegende Frage ist, ob ein abgeschlossenes Versprechen sofort einen Rückruf ausführen soll, wenn das Versprechen bereits abgeschlossen ist. Ich kann mir viele Gründe vorstellen, dass dies passieren kann - zum Beispiel eine asynchrone Sicherungsprozedur, die nur dann Daten speichert, wenn Änderungen vorgenommen wurden. Es ist möglicherweise in der Lage, Änderungen von der Clientseite aus synchron zu erkennen, ohne dass eine externe Ressource durchlaufen werden muss. Wenn Änderungen jedoch erkannt werden, ist nur dann ein asynchroner Vorgang erforderlich.

In anderen Umgebungen mit asynchronen Aufrufen scheint das Muster so zu sein, dass der Entwickler dafür verantwortlich ist, dass seine Arbeit sofort abgeschlossen werden kann (dies wird beispielsweise durch die Implementierung des asynchronen Musters durch .NET Framework unterstützt). Dies ist kein Designproblem des Frameworks, es ist die Art und Weise, wie es implementiert wird.

Die Entwickler von JavaScript (und viele der oben genannten Kommentatoren) scheinen diesbezüglich einen anderen Standpunkt zu haben. Sie bestehen darauf, dass etwas asynchron sein muss und immer asynchron sein muss. Ob dies "richtig" ist oder nicht, ist unerheblich - gemäß der Spezifikation, die ich unter https://promisesaplus.com/ gefunden habe, gibt Punkt 2.2.4 an, dass grundsätzlich keine Callbacks aufgerufen werden können, bis Sie nicht mehr das erreichen, was ich bezeichne als "Skriptcode" oder "Benutzercode"; Das heißt, die Spezifikation besagt eindeutig, dass der Callback nicht sofort aufgerufen werden kann, wenn das Versprechen erfüllt ist. Ich habe ein paar andere Orte überprüft und sie sagen entweder nichts zu dem Thema oder stimmen mit der Originalquelle überein. Ich weiß nicht, ob https://promisesaplus.com/ in dieser Hinsicht als endgültige Informationsquelle angesehen werden könnte, aber keine anderen Quellen, die ich nicht als übereinstimmend betrachtet habe, und die scheint die vollständigste zu sein.

Diese Einschränkung ist etwas willkürlich und ich bevorzuge die .NET-Perspektive. Ich überlasse es anderen, zu entscheiden, ob sie es für "schlechten Code" halten, etwas zu tun, das synchron oder asynchron aussehen könnte.

Ihre eigentliche Frage ist, ob Bluebird für das Nicht-JavaScript-Verhalten konfiguriert werden kann. In Bezug auf die Leistung ist dies möglicherweise von geringem Nutzen. In JavaScript ist alles möglich, wenn Sie sich intensiv genug darum bemühen. Da das Promise-Objekt jedoch plattformübergreifender wird, wird es eine Verschiebung zur Verwendung als native Komponente geben, statt als benutzerdefinierte Komponente Polyfills oder Bibliotheken. Wie auch immer die Antwort heute lautet, die Überarbeitung eines Versprechens in Bluebird wird Sie in der Zukunft wahrscheinlich zu Problemen führen, und Ihr Code sollte wahrscheinlich nicht so geschrieben sein, dass er von einem Versprechen abhängig ist oder sofort gelöst wird.

17
Joe Friesenhan

Sie denken vielleicht, dass dies ein Problem ist, weil es keine Möglichkeit gibt, zu haben

getSomeText('first').then(print);
print('second');

und getSomeText"first" vor "second" drucken, wenn die Auflösung synchron ist. 

Aber ich glaube, du hast ein logisches Problem.

Wenn Ihre getSomeText-Funktion je nach Kontext möglicherweise synchron oder asynchron ist, sollte sie die Ausführungsreihenfolge nicht beeinflussen. Sie verwenden Versprechen, um sicherzustellen, dass es immer das gleiche ist. Die Ausführung einer variablen Reihenfolge würde wahrscheinlich zu einem Fehler in Ihrer Anwendung führen.

Benutzen

getSomeText('first') // may be synchronous using cast or asynchronous with ajax
.then(print)
.then(function(){ print('second') });

In beiden Fällen (synchron mit cast oder asynchroner Auflösung) haben Sie die korrekte Ausführungsreihenfolge.

Beachten Sie, dass eine Funktion manchmal synchron ist und manchmal nicht ungewöhnlich oder unwahrscheinlich ist (denken Sie an Cache-Behandlung oder Pooling). Sie müssen nur annehmen, dass es asynchron ist, und alles wird immer in Ordnung sein.

Aber den Benutzer der API mit einem booleschen Argument zu fragen, ob die Operation asynchron sein soll, erscheint nicht sinnvoll, wenn Sie den Bereich von JavaScript nicht verlassen (dh, wenn Sie keinen nativen Code verwenden) ).

8
Denys Séguret

Die Versprechungen sollen den asynchronen Code einfacher machen, d. H. Näher an dem, was Sie fühlen, wenn Sie synchronous - Code verwenden.

Sie verwenden synchronen Code. Machen Sie es nicht komplizierter.

function print( text ){

    console.log( 'print -> %s', text );

    return text;
}

function getSomeTextSimpleCast( opt_text ){

    var text = opt_text || 'Some fancy text-value';

    return text;
}

print(getSomeTextSimpleCast('first'));
print('second');

Und das sollte das Ende sein.


Wenn Sie dieselbe asynchrone Schnittstelle beibehalten möchten, obwohl Ihr Code synchron ist, müssen Sie dies vollständig tun.

getSomeTextSimpleCast('first')
    .then(print)
    .then(function() { print('second'); });

then ruft Ihren Code aus dem normalen Ausführungsablauf ab, da er asynchron sein soll. Bluebird macht es richtig. Eine einfache Erklärung dessen, was es tut:

function then(fn) {
    setTimeout(fn, 0);
}

Beachten Sie, dass Bluebird das nicht wirklich tut, es ist nur ein einfaches Beispiel.

Versuch es!

then(function() {
    console.log('first');
});
console.log('second');

Dadurch wird Folgendes ausgegeben:

second
first 
7

Hier gibt es schon einige gute Antworten, aber um es kurz und bündig zusammenzufassen:

Ein Versprechen (oder eine andere asynchrone API), das manchmal asynchron und manchmal synchron ist, ist eine schlechte Sache.

Sie denken vielleicht, dass dies in Ordnung ist, weil der erste Aufruf der API einen Boolean-Vorgang erfordert, um zwischen sync/async zu wechseln. Aber was ist, wenn das in einem Wrapper-Code steckt und die Person, die that - Code verwendet, nicht über diese Phänomene Bescheid weiß? Sie haben einfach unverschuldetes Verhalten mit sich gebracht.

Unterm Strich: Versuchen Sie das nicht. Wenn Sie synchrones Verhalten wünschen, geben Sie kein Versprechen zurück.

Damit hinterlasse ich Ihnen dieses Zitat aus Sie kennen JS nicht :

Ein anderes Vertrauensproblem wird als "zu früh" bezeichnet. In anwendungsspezifischen Begriffen kann dies tatsächlich dazu führen, dass einige wichtige Aufgaben abgeschlossen werden. Im Allgemeinen ist das Problem jedoch in Dienstprogrammen offensichtlich, die entweder den jetzt bereitgestellten Rückruf (synchron) oder später (asynchron) aufrufen können.

Dieser Nichtdeterminismus um das sync-async-Verhalten führt fast immer dazu, dass Bugs nur sehr schwer zu finden sind. In einigen Kreisen wird das fiktive Wahnsinns-Monster namens Zalgo verwendet, um die Sync/Async-Albträume zu beschreiben. "Lass Zalgo nicht los!" ist ein verbreiteter Schrei und führt zu sehr guten Ratschlägen: Rufen Sie Callbacks immer asynchron auf, auch wenn dies in der nächsten Runde der Ereignisschleife "sofort" ist, sodass alle Callbacks vorhersehbar asynchron sind.

Hinweis: Weitere Informationen zu Zalgo finden Sie unter Oren Golans "Lass Zalgo nicht los!" ( https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md ) und Isaac Z. Schlueters "Designing APIs for Asynchrony" ( http: // blog .izs.me/post/59142742143/designing-apis-für-asynchronie ).

Erwägen:

function result(data) {
    console.log( a );
}

var a = 0;

ajax( "..pre-cached-url..", result );
a++;`

Wird dieser Code 0 (Aufruf des synchronisierten Rückrufs) oder 1 (Aufruf des asynchronen Rückrufs) drucken? Kommt drauf an ... von den Bedingungen.

Sie können sehen, wie schnell die Unberechenbarkeit von Zalgo jedes JS-Programm bedrohen kann. Das dumm klingende "Never Release Zalgo" ist also ein unglaublich allgemeiner und solider Rat. Immer asynchron sein.

2
JLRishe

Was ist mit diesem Fall, auch CrmFetchKit bezogen, der in der neuesten Version Bluebird verwendet. Ich habe ein Upgrade von Version 1.9 vorgenommen, die auf jQuery basierte. Der alte App-Code, der CrmFetchKit verwendet, verfügt jedoch über Methoden, deren Prototypen ich nicht ändern kann.

Bestehender App-Code

CrmFetchKit.FetchWithPaginationSortingFiltering(query.join('')).then(
    function (results, totalRecordCount) {
        queryResult = results;

        opportunities.TotalRecords = totalRecordCount;

        done();
    },
    function err(e) {
        done.fail(e);
    }
);

Alte CrmFetchKit-Implementierung (eine benutzerdefinierte Version von fetch ())

function fetchWithPaginationSortingFiltering(fetchxml) {

    var performanceIndicator_StartTime = new Date();

    var dfd = $.Deferred();

    fetchMore(fetchxml, true)
        .then(function (result) {
            LogTimeIfNeeded(performanceIndicator_StartTime, fetchxml);
            dfd.resolve(result.entities, result.totalRecordCount);
        })
        .fail(dfd.reject);

    return dfd.promise();
}

Neue CrmFetchKit-Implementierung

function fetch(fetchxml) {
    return fetchMore(fetchxml).then(function (result) {
        return result.entities;
    });
}

Mein Problem ist, dass die alte Version die dfd.resolve (...) hatte, bei der ich eine beliebige Anzahl von Params übergeben konnte, die ich brauche.

Die neue Implementierung kehrt einfach zurück, das übergeordnete Element scheint den Rückruf aufzurufen. Ich kann es nicht direkt aufrufen.

In der neuen Implementierung habe ich eine angepasste Version des fetch () erstellt

function fetchWithPaginationSortingFiltering(fetchxml) {
    var thePromise = fetchMore(fetchxml).then(function (result) {
        thePromise._fulfillmentHandler0(result.entities, result.totalRecordCount);
        return thePromise.cancel();
        //thePromise.throw();
    });

    return thePromise;
}

Aber das Problem ist, dass der Rückruf zweimal aufgerufen wird, einmal, wenn ich es explizit mache, und zweitens durch das Framework, aber es nur einen Parameter übergibt. Um es auszutricksen und "zu sagen", nichts anzurufen, weil ich es explizit mache, versuche ich .cancel () aufzurufen, aber es wird ignoriert. Ich habe verstanden, warum, aber immer noch, wie Sie die "dfd.resolve (result.entities, result.totalRecordCount)" ausführen. in der neuen Version, ohne Prototypen in der App ändern zu müssen, die diese Bibliothek verwendet?

0
Nicolas