12

Dynamics CRM REST/ODATA Web サービス (CrmRestKit)用の小さなライブラリを開発しました。lib は jQuery に依存し、jQuery の promise-like-pattern である promise-pattern を利用します。

今、私はこのライブラリをbluebirdに移植し、jQuery の依存関係を削除したいと考えています。しかし、bluebird は promise オブジェクトの同期解決をサポートしていないため、問題に直面しています。

いくつかのコンテキスト情報:

CrmRestKit の API は、Web サービス呼び出しを同期モードまたは非同期モードで実行する必要があるかどうかを定義するオプションのパラメーターを除きます。

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

「true」を渡すか、最後のパラメーターを省略すると、メソッドはレコードを同期して作成します。モード。

場合によっては、同期モードで操作を実行する必要があります。たとえば、フォームの保存イベントに関与する Dynamics CRM の JavaScript コードを記述し、このイベント ハンドラーで検証のために同期操作を実行する必要があります (たとえば、特定の数の子レコードが存在することを検証し、適切な数のレコードが存在する場合は、保存操作をキャンセルしてエラー メッセージを表示します)。

私の問題は次のとおりです。bluebird は同期モードでの解像度をサポートしていません。たとえば、次のようにすると、「then」ハンドラーが非同期で呼び出されます。

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

出力は次のとおりです。

print -> second
print -> first

promise は既に値で解決されているため、「second」は「first」の後に表示されると予想されます。したがって、既に解決された promise-object に適用されると、 then-event-handler がすぐに呼び出されると想定します。

jQuery を使用して同じことを行うと (既に解決されたプロミスで使用)、期待どおりの結果が得られます。

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

これにより、次の出力が生成されます。

print -> third
print -> fourth

ブルーバードを同じように機能させる方法はありますか?

更新: 提供されたコードは、問題を説明するためのものです。lib の考え方は次のとおりです。実行モード (同期、非同期) に関係なく、呼び出し元は常に promise オブジェクトを処理します。

「... ユーザーに尋ねる...意味がないようです」について: 「CreateAsync」と「CreateSync」の 2 つのメソッドを提供する場合、操作の実行方法を決定するのもユーザー次第です。

とにかく、現在の実装では、デフォルトの動作 (最後のパラメーターはオプション) は非同期実行です。したがって、コードの 99% は promise-object を必要とします。オプションのパラメーターは、単に同期実行が必要な 1% のケースでのみ使用されます。さらに、私は自分で lib を開発し、99,9999% のケースで async モードを使用していますが、必要に応じて sync-road に移動するオプションがあると便利だと思いました。

しかし、同期メソッドは単に値を返す必要があるという点は理解できたと思います。次のリリース (3.0) では、「CreateSync」と「CreateAsync」を実装します。

ご意見ありがとうございます。

Update-2 オプションのパラメーターの意図は、一貫した動作を保証し、論理エラーを防ぐことでした。libを使用する私の方法「GetCurrentUserRoles」の消費者としてあなたを想定してください。したがって、メソッドは常にプロミスを返します。つまり、結果に依存するコードを実行するには、「then」メソッドを使用する必要があります。したがって、一部の人がこのようなコードを書くとき、私はそれが完全に間違っていることに同意します:

var currentUserRoels = null;

GetCurrentUserRoles().then(function(roles){

    currentUserRoels = roles;
});

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

    // ...
}

メソッド「GetCurrentUserRoles」が同期から非同期に変更されると、このコードが壊れることに同意します。

しかし、これは良い設計ではないことは理解しています。なぜなら、消費者は非同期メソッドを扱う必要があるからです。

4

6 に答える 6

20

短いバージョン: なぜそうしたいのかはわかりますが、答えはノーです。

根本的な質問は、promise が既に完了している場合、完了した promise がすぐにコールバックを実行する必要があるかどうかだと思います。これが発生する理由はたくさん考えられます。たとえば、変更が加えられた場合にのみデータを保存する非同期保存手順などです。外部リソースを介さずにクライアント側からの変更を同期的に検出できる場合がありますが、変更が検出された場合にのみ、非同期操作が必要になります。

非同期呼び出しがある他の環境では、作業がすぐに完了する可能性があることを開発者が理解する責任があるというパターンのようです (たとえば、.NET フレームワークの非同期パターンの実装はこれに対応しています)。これはフレームワークの設計上の問題ではなく、実装方法の問題です。

JavaScript の開発者 (および上記のコメント投稿者の多く) は、これについて別の見方をしているようで、何かが非同期である可能性がある場合、それは常に非同期でなければならないと主張しています。これが「正しい」かどうかは重要ではありません - https://promisesaplus.com/で見つけた仕様によると、アイテム 2.2.4 は、基本的に、私が参照するものから離れるまで、コールバックを呼び出すことはできないと述べています。 「スクリプトコード」または「ユーザーコード」として。つまり、Promise が完了したとしても、すぐにコールバックを呼び出すことはできないことが仕様に明確に記載されています。私は他のいくつかの場所をチェックしましたが、彼らはトピックについて何も言っていないか、元の情報源に同意しています. https://promisesaplus.com/ _この点で決定的な情報源と見なすことができますが、私が見た他の情報源はそれに反対しておらず、最も完全なようです.

この制限はいくぶん恣意的なものであり、私は率直に言って、これについて .NET の観点を好みます。非同期に見える方法で同期であるかどうかに関係なく何かを実行することを「悪いコード」と見なすかどうかの判断は、他の人に任せます。

あなたの実際の質問は、Bluebird が JavaScript 以外の動作を行うように構成できるかどうかです。パフォーマンスに関しては、そうすることにわずかな利点があるかもしれません.JavaScriptでは、十分に努力すれば何でも可能ですが、Promiseオブジェクトがプラットフォーム全体でよりユビキタスになるにつれて、カスタム作成ではなくネイティブコンポーネントとして使用することにシフトすることがわかります.ポリフィルまたはライブラリ。そのため、今日の答えが何であれ、Bluebird で promise を作り直すと、将来的に問題が発生する可能性が高く、promise に依存したり、promise の即時解決を提供するようにコードを記述しないでください。

于 2015-01-15T19:30:06.250 に答える
8

これは問題だと思うかもしれません。

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

解像度が同期するgetSomeText "first"前に印刷したこと。"second"

しかし、あなたには論理的な問題があると思います。

関数がコンテキストに応じてgetSomeText同期または非同期である場合、実行順序に影響を与えるべきではありません。promise を使用して、常に同じであることを保証します。実行順序が可変であると、アプリケーションのバグになる可能性があります。

使用する

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

どちらの場合 (同期cast解決または非同期解決) でも、正しい実行順序が得られます。

関数が同期する場合とそうでない場合があることに注意してください (キャッシュ処理またはプーリングについて考えてみてください)。非同期であると仮定するだけで、すべてが常に問題ありません。

しかし、API のユーザーに、操作を非同期にしたい場合にブール値の引数で正確に要求することは、JavaScript の領域を離れない場合 (つまり、ネイティブ コードを使用しない場合) には意味がないように思われます。 )。

于 2014-01-16T08:36:26.070 に答える
7

約束のポイントは、非同期コードをより簡単にすることです。つまり、同期コードを使用するときに感じるものに近づけることです。

同期コードを使用しています。もっと複雑にしないでください。

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

そして、それで終わりのはずです。


コードが同期であっても、同じ非同期インターフェイスを維持したい場合は、それをずっと行う必要があります。

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

then非同期であるはずなので、通常の実行フローからコードを取得します。ブルーバードはそこで正しい方法でそれを行います。それが何をするかの簡単な説明:

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

bluebird は実際にはそうではないことに注意してください。単純な例を示すだけです。

それを試してみてください!

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

これにより、次のように出力されます。

second
first 
于 2014-01-16T08:07:33.977 に答える
3

ここにはすでにいくつかの良い答えがありますが、問題の要点を非常に簡潔に要約すると:

非同期の場合もあれば同期の場合もある promise (または他の非同期 API) を持つことは悪いことです。

API への最初の呼び出しではブール値を使用して同期/非同期を切り替えるため、問題ないと思うかもしれません。しかし、それが一部のラッパー コードに埋もれていて、そのコードを使用している人がこれらの悪ふざけを知らない場合はどうなるでしょうか。彼らは、自分のせいではなく、予測不可能な行動をとってしまったのです。

結論:これをやろうとしないでください。同期動作が必要な場合は、promise を返さないでください。

それで、 You Don't Know JSからのこの引用を残します。

もう1つの信頼の問題は、「早すぎる」と呼ばれています。アプリケーション固有の用語で言えば、実際には、重要なタスクが完了する前に呼び出される場合があります。しかし、より一般的には、提供するコールバックを現在 (同期的に) または後で (非同期的に) 呼び出すことができるユーティリティで問題が明らかです。

同期または非同期動作に関するこの非決定論は、ほとんどの場合、バグを追跡するのが非常に困難になります。一部のサークルでは、Zalgo という名前の架空の狂気を誘発するモンスターが、同期/非同期の悪夢を説明するために使用されています。「ザルゴを離すな!」は一般的な叫び声であり、非常に健全なアドバイスにつながります。イベント ループの次のターンで「すぐに」コールバックを呼び出す場合でも、常にコールバックを非同期で呼び出して、すべてのコールバックが予測どおりに非同期になるようにします。

注: Zalgo の詳細については、Oren Golan の「Don't Release Zalgo!」を参照してください。( https://github.com/oren/oren.github.io/blob/master/posts/zalgo.md ) および Isaac Z. Schlueter の「Designing APIs for Asynchrony」( http://blog.izs.me/post /59142742143/designing-apis-for-asynchrony )。

検討:

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

var a = 0;

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

このコードは 0 (同期コールバック呼び出し) または 1 (非同期コールバック呼び出し) を出力しますか? 条件によります。

Zalgo の予測不可能性が JS プログラムを脅かす可能性があることがすぐにわかります。したがって、ばかげて聞こえる「決してザルゴをリリースしないでください」は、実際には信じられないほど一般的で堅実なアドバイスです. 常に非同期にします。

于 2015-02-12T21:45:41.130 に答える
0

この場合、最新バージョンでBluebirdを使用するCrmFetchKit関連もどうですか。jQueryベースのバージョン1.9からアップグレードしました。まだ CrmFetchKit を使用する古いアプリ コードには、私が変更できない、または変更しないプロトタイプのメソッドがあります。

既存のアプリ コード

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

        opportunities.TotalRecords = totalRecordCount;

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

古い CrmFetchKit 実装 (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();
}

新しい CrmFetchKit の実装

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

私の問題は、古いバージョンに dfd.resolve(...) があり、必要な数のパラメーターを渡すことができたことです。

新しい実装が返されるだけで、親がコールバックを呼び出しているようで、直接呼び出すことはできません。

新しい実装で fetch() のカスタム バージョンを作成しました。

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

    return thePromise;
}

しかし、問題は、コールバックが 2 回呼び出されることです。1 回目は明示的に実行し、2 回目はフレームワークによって呼び出されますが、1 つのパラメーターのみが渡されます。それをだまして、何も呼び出さないように「伝える」には、明示的に行うので.cancel()を呼び出そうとしますが、無視されます。理由はわかりましたが、「dfd.resolve(result.entities、result.totalRecordCount);」をどのように行うのですか?このライブラリを使用するアプリでプロトタイプを変更する必要なく、新しいバージョンで ?

于 2016-05-19T19:12:16.953 に答える