ホームページ  >  記事  >  ウェブフロントエンド  >  jQuery、Angular、nodeにおけるPromiseの詳しい解説

jQuery、Angular、nodeにおけるPromiseの詳しい解説

php中世界最好的语言
php中世界最好的语言オリジナル
2018-03-16 13:36:401594ブラウズ

今回は、jQuery、Angular、node での Promise について詳しく説明します。 jQuery、Angular、node で Promise を使用する際の 注意事項 について、実際のケースを見てみましょう。

私が初めて Promise に出会ったのは jQuery 1.5 で導入された非同期キュー モジュールであり、非同期タスクと

コールバック関数 を分離するために使用されます。 ajaxモジュール、queueモジュール、readyイベントの基本機能を提供します。 jQueryを使ってDOMを操作する場合、あまりPromiseを使いたくないのですが、最近nodeやAngularを勉強していて、jsを使ってビジネスロジックやデータ操作のコードを書く必要が出てきました。 一般的に言えば、ユーザーの行動は本質的に分散しているため、イベントはインタラクティブなシナリオでの使用に適しており、プロミスのようなプロセス制御はバックグラウンド ロジックでビジネスを処理するのに適しています

    //jQuery1.5之前    $.ajax({
        url: 'url',
        success: function(data, status, xhr) {},
        error: function(xhr, status, msg) {},
        complete: function(xhr, status) {}
    });
現在推奨されている書き方は、

    $.ajax('url')
        .done(function (data, statusText, xhr) { })
        .done(function (data, statusText, xhr) { })
        .fail(function (data, statusText, error) { })
        .fail(function (data, statusText, error) { })
        .complete(function (xhr, statusText) { })
        .complete(function (xhr, statusText) { });
Promiseを使う理由

違いは何でしょうか? 前者はコールバック関数を使用し、後者はチェーン構文を使用し、繰り返し呼び出すことができるようです。 JavaScript の実行はシングルスレッドで非同期です。非同期プログラミングを処理するには、通常、コールバック関数、イベントのパブリッシング/サブスクリプション、および Promise/Deferred の 3 つの方法があります。端的に言えば、

関数実行のプロセスをより適切に制御するためです。コールバック関数の使用は簡単ですが、コールバックにより呼び出しに一貫性がなくなり、他のコールバックに依存するとコードのフローが改ざんされる可能性があり、デバッグが非常に困難になります。プラグインまたは SDK を作成する場合、コールバック関数が大きすぎると問題が発生します。イベントに関しては、当然コールバック関数よりもはるかにクリーンですが、イベント シナリオはメソッド A が実行されるときの通知に似ています。イベントが発生すると、サブスクライバは通知を受信した後すぐに作業を開始します。イベントとコールバック関数は両方とも受動的であり、実行ステータス (成功または失敗) の区別はありません。したがって、必要に応じて、より多くのコールバックを記述するか、より多くのイベントをサブスクライブする必要があります。コードプロセス (コントローラー/サービスなど) で非同期メソッドを呼び出して特定の結果を取得するには、Promise が必要です。 「あなたの言うとおりにする」という意味のようであるため、この名前も熟考する価値があります。

jQueryのお約束

jqueryの非同期キューには、jQuery.Callbacks(flag)、jQuery.Deferred(func)、およびjQuery.when()が含まれます。 Deferred はコールバックに基づいて実装され、jquery.when() は最初の 2 つに基づいています。

Promise は、非同期キューの読み取り専用コピーです

jQuery.Callbacks(flag)

jQuery.Callbacks(flag) は、一連のコールバック関数を管理するためのチェーン ツール オブジェクトを返します。コールバック関数は配列を通じて内部的に保存され、他のメソッドはこの配列を中心に動作および検出します。 add()、remove()、fireWith/fire()/fired()、disable()/disabled()、lock/locked メソッドを提供します。

callbacks.add()は、1つまたはコールバック関数のグループをコールバック関数リストに追加するために使用されます callbacks.remove() は、1つまたはコールバック関数のグループを削除するために使用されますコールバック関数リストから コールバック関数 callbacks.fireWith(context,args) 指定されたコンテキストとパラメータを使用して、コールバック関数リスト内のすべてのコールバック関数をトリガーします callbacks.fire()コールバック関数をトリガーするために指定されたパラメータ すべてのコールバック関数 callbacks.fired() は関数リストがトリガーされたかどうかを判断するために使用されます callbacks.disable() 無効化されたコールバック関数リスト callbacks.disabled() コールバック関数が無効かどうかを判断するために使用されます callbacks.lock() コールバック関数をロックするために使用されます (メモリ モードでコールバック関数のコンテキストとパラメーターをロックします) callback.locked() ロックされているかどうかを判断するために使用されます

 可以跑一下这些示例加深理解:http://www.cnblogs.com/lmule/p/3463515.html 这就不赘述了。

jQuery.Deferred(func)

jQuery.Deferred在jQuery.Callbacks(flags)的基础上为回调函数增加了三种状态:Pending(待定),resolved(成功)和rejected(失败)。它内部维护了三个回调函数列表,分别是成功回调函数列表、失败回调函数列表和消息回调函数列表。调用方法defferred.resolve(args)和resolveWith(context,args)将改变异步队列的状态为成功状态,并会立即执行添加的所有成功回调函数。反之,调用deferred.reject和deferred.rejectWith(context,args)会进入失败状态并触发所有失败的回调函数。一旦进入失败或成功状态,就会保持状态不变。也就是说再次调用deferred.reject()或deferred.resolve()会被忽略.

 tuples =
                [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved""reject", "fail", jQuery.Callbacks("once memory"), "rejected""notify", "progress", jQuery.Callbacks("memory"

提供的主要方法如下:

deferred.done(doneCallback[,doneCallback]) 添加成功回调函数,当异步队列处于成功时调用
deferred.fail(failCallback[,failCallback]) 添加失败回调函数,当异步队列处于失败时调用
deferred.process(callback) 添加消息回调函数  
deferred.then(doneCallback,failCallback,[,processCallback]) 同时添加成功、失败、消息回调函数。
deferred.always(callback[,callback]) 添加回调函数,当异步队列处于成功或失败的时候调用
deferred.resolve(args) 使用指定参数调用所有成功函数,队列进入成功状态
deferred.resolveWith(context[,args]) 同上,指定了上下文。
deferred.reject(args) 使用指定参数执行所有失败回调函数,队列进入失败状态
deferred.rejectWith(context[,args]) 同上,指定了上下文。
deferred.notify(args) 调用所有消息回调函数
deferred.notifyWith(context[,args]) 同上,指定了上下文。
deferred.state() 判断异步队列状态  
deferred.promise([taget]) 返回当前deferred对象的只读副本,或者为普通对象增加异步队列的功能

所谓只读副本就是只暴露了添加回调函数和判断方法,done(),fail(),then()等,而不包含触发执行和改变状态的方法:resolve(),rejecet(),notify()等。也就是说,你把事情(回调)交代给promised对象,然后它去做就行了。

jQuery.when(deferreds)

jQuery.when方法提供了一个或多个对象来执行回调函数的功能,通常是具有异步事件的异步队列。也就是说它可以支持同时给多个耗时的操作添加回调函数。

   var doneCallback = function () { console.log('done', arguments); }    var failCallback = function () { console.log('fail', arguments); }
    $.when($.ajax('/Home/Test?id=1'), $.ajax('/Home/Test?id=2')).done(doneCallback).fail(failCallback);

如果都执行成功会进入到done,只要有失败就会进入fail。jQuery.when()都会返回它的只读副本。也就是一个promise。

更多示例可以移步:阮一峰的博客:jQuery的deferred对象详解

ajax中的实现

在Ajax中是将jqXHR对象增加了异步队列的功能,从下面的源码可以看到调用done和success是等价的,同理对于fail和error.

  deferred = jQuery.Deferred(),
  completeDeferred = jQuery.Callbacks("once memory"),
   //..

 // Attach deferreds
        deferred.promise( jqXHR ).complete = completeDeferred.add;
        jqXHR.success = jqXHR.done;
        jqXHR.error = jqXHR.fail;

jqxhr:

 当ajax执行完后,触发回调函数:

        // Success/Error
            if ( isSuccess ) {
                deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
            } else {
                deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
            }            // Status-dependent callbacks            jqXHR.statusCode( statusCode );
            statusCode = undefined;            if ( fireGlobals ) {
                globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
                    [ jqXHR, s, isSuccess ? success : error ] );
            }            // Complete
            completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

ajax的例子就不用说了,开篇就是。

队列中的实现

队列是一种先进先出的数据结构,jQuery的队列提供了.queue()/jQuery.queue()和dequeue()/jQuery.deueue()实现入队和出队操作,这些是基于数据缓存模块和数组实现。队列提供了一个promise(type,object)方法,返回一个异步队列的只读副本,来观察对应的队列是否执行完成。

        promise: = 1== = =  ( !( -- (  type !== "string"=== type || "fx"( i--= jQuery._data( elements[ i ], type + "queueHooks" ( tmp &&++

运用:

<p id="queue" style=" background-color: wheat;color: green;width: 200px;">queue</p>
  $("#queue").fadeOut(800).delay(1200).fadeIn().animate({ width: 100 }).animate({ height: 100 })
        .promise().done(function() {
            console.log('done!');
        });

默认情况下,animate和fade都进入了队列,所有是按顺序执行的。通过设置queue可以让animate的动画立即执行。

 $("#queue").fadeIn(800).delay(1200).fadeOut().animate({ width: 100 }, { queue: false }).animate({ height: 100 }, { queue: false })
    .promise().done(function() {
            console.log('done!');
        })

如果是多个元素动画:

  <p id="queue" style="display: none;background-color: wheat;color: green;width: 200px;">queue</p>
        <p id="queue1" style="background-color: wheat;color: green;width: 200px;">queue</p>
        <p id="queue2" style="background-color: wheat;color: green;width: 200px;">queue</p>
$("#queue").fadeOut(800,function() {
        console.log("fadeout,do some thing");
        $("#queue1").animate({ width: '100px' }, 2000, function() {
            console.log("queue1,do some thing");
            $('#queue2').animate({
                height: 100
            }, 2000,function() {
                console.log("queue2,do some thing");
            });
        });
    })

一个嵌套一个,有么有感觉像是麻花。可以用promise改造成这样:

   var p1 = $("#queue").fadeOut(800).promise();    var p2 = $("#queue1").animate({ width: 100 }, 2000).promise();    var p3 = $("#queue2").animate({ height: 100 }, 2000).promise();
    $.when(p1).then(p2).then(p3);

是不是很清爽。自定义的正确格式是:

   var async = function (key) {        var deferred = $.Deferred();        function dowork() {            if (key == 1) {
                deferred.resolve("success!");
            } else {
                deferred.reject("fail!");
            }
        }
        setTimeout(dowork, 2000);        return deferred.promise();
    }

就是三步,获取一个deferred,使用resolve/rejcet在内部决定状态,然后返回promise,调用:

async(2).done(function (res) {
        console.log(res);
    }).fail(function (res) {
        console.log(res);
    });

有了上面这些,我们再看去看看Angular和node中的promise。 

Angular中的promise

 promise 是一种用异步方式处理值的方法,promise是对象,代表了一个函数最终可能的返回值或抛出的异常。在与远程对象打交道非常有用,可以把它们看成一个远程对象的代理。

要在Angular中创建promise需要使用内置的$q服务。先用factory定义一个服务,注入$q服务。

angular.module('readApp').factory('asyncService', [    "$q", function ($q) {         var myAsync=function(flag) {            var deferred = $q.defer();            if (flag) {
                deferred.resolve("well done!");
            } else {
                deferred.reject("lost!");
            }             return deferred.promise;
         }        return {
            myAsync: myAsync
        };
    }
]);

获得deferred的方法和jquery不同,但resolve和reject是一样的,最后返回的是promise属性,而不是promise方法。再看如何调用:

angular.module('readApp').controller('testCtrl', ["$scope", "asyncService", function ($scope, asyncService) {
        $scope.flag = true;
        $scope.handle = function () {            asyncService.myAsync($scope.flag).then(function (result) {
                $scope.status = result;                return result;
            }, function (error) {
                $scope.status = error;                return error;
            });
        }
    }])

获取到服务后,调用then方法。then有三个参数,分别对应成功回调、失败回调和通知回调。这个和jquery是一致的。

html:

<p  class="container">
    <label for="flag">成功        <input type="checkbox" id="flag" ng-model="flag" name="name" /> <br />
        <p>{{status}}</p>
        <button ng-click="handle()">点击</button>
    </label></p><footer-n

结果:

不同的是,Angular的promise没有公布jquery那么多方法,我们可以看一下deferred.promise这个属性,它是一个$$state对象。根据Promise/A规范,一个Promise只要具备一个then方法即可。

注意到,Angular中的deferred有notify、reject、resolve三个主要方法和一个promise属性,而这个promise的原型连中包含了我们调用的then方法,then方法在执行完之后会派生一个新的promise,因此可以链式调用。没有done和fail,但是还提供了catch和finally方法。catch就相当于是error方法了。而finally方法就像强类型语言中的场景一样,当我们需要释放一个资源,或者是运行一些清理工作,不管promise是成功还是失败时,这个方法会很有用。要注意的是finally是ie中的一个保留字,需要下面这样调用:

promise[&#39;finally&#39;](function() {});

除了defer()方法,$q还有all和when方法,all(promises)可以将多个promise合并成一个,但如果任意一个promise拒绝了,那么结果的promise也会拒绝。而when(value)方法把一个可能是值或者promise包装成一个$q promise。有了jQuery中的when,这两个方法不难理解。关于这三个方法的示例可以参考这篇博客:AngularJS 中的Promise --- $q服务详解

Angular的$q的灵感是来自[Kris Kowal's Q],从官方的注释中可以看到

 * This is an implementation of promises/deferred objects inspired by
 * [Kris Kowal&#39;s Q](https://github.com/kriskowal/q).

  * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred
  * implementations, and the other which resembles ES6 promises to some degree.

支持两种风格,可以像Q库或者jQuery的deferred一样,也可以用ES6语法,文档给出了示例,也是就构造函数法来定义:

 var asyncGreet = function (name) {            return $q(function (resolve, reject) {
                console.log(resolve, reject);
                setTimeout(function () {                    if (name=="stone") {
                        resolve(&#39;Hello, &#39; + name + &#39;!&#39;);
                    } else {
                        reject(&#39;Greeting &#39; + name + &#39; is not allowed.&#39;);
                    }
                }, 1000);
            });
        };

通知(notify/progress)回调还不支持这种写法。对比看,没太大差别。

  function asyncGreet(name) {    var deferred = $q.defer();
    setTimeout(function() {     deferred.notify(&#39;About to greet &#39; + name + &#39;.&#39;);
     if (okToGreet(name)) {        deferred.resolve(&#39;Hello, &#39; + name + &#39;!&#39;);      } else {        deferred.reject(&#39;Greeting &#39; + name + &#39; is not allowed.&#39;);      }     }, 1000);
   return deferred.promise;  }

大致看下源码如何实现:

Promise:

 function Promise() {    this.$$state = { status: 0 };
  }
  extend(Promise.prototype, {
    then: function(onFulfilled, onRejected, progressBack) {      if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) {        return this;
      }      var result = new Deferred();      this.$$state.pending = this.$$state.pending || [];      this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]);      if (this.$$state.status > 0) scheduleProcessQueue(this.$$state);      return result.promise;
    },    "catch": function(callback) {      return this.then(null, callback);
    },    "finally": function(callback, progressBack) {      return this.then(function(value) {        return handleCallback(value, true, callback);
      }, function(error) {        return handleCallback(error, false, callback);
      }, progressBack);
    }
  });

创建了一个Promise对象包含一个$$state属性,然后扩展了then,catch,finally方法(注意后两个带了引号)。then的三个参数都是回调函数,对应成功、失败、通知回调,并在then方法中创建了一个deferred作为结果,将回调函数和创建的deferred都存入了数组,主意到这是一个二维数组,每个then对应的promise和回调函数都在这个数组里面。最后返回promise。而catch和finally内部也是调用的then。只要状态大于0也就promise获得了结果就用scheduleProcessQueue处理回调。 Deferred 内部包含了一个promise以及resolve、reject和notify三个方法。jQuery.deferred 中处理的是三个回调队列,Angular中处理的一个是二维数组。 

$http的是一个promise对象:

  var promise = $q.when(config);       //some code       
        promise = promise.then(thenFn, rejectFn);
      }      if (useLegacyPromise) {
        promise.success = function(fn) {
          assertArgFn(fn, 'fn');
          promise.then(function(response) {
            fn(response.data, response.status, response.headers, config);
          });          return promise;
        };
        promise.error = function(fn) {
          assertArgFn(fn, 'fn');
          promise.then(null, function(response) {
            fn(response.data, response.status, response.headers, config);
          });          return promise;
        };
      } else {
        promise.success = $httpMinErrLegacyFn('success');
        promise.error = $httpMinErrLegacyFn('error');
      }      return promise;

用then扩展了error和succes方法,因此我们可以这样使用:

 booksData.getbookById(bookid).success(function(data) {
            vm.book = data;
        }).error(function (e) {
            console.log(e);
            vm.message = "Sorry, something's gone wrong ";
        });

$Interval也是一个promise对象。

Node中的promise

Q

 node中有很多耗时的回调写法,比如写入文件,请求api,查询数据库。node比较早的就是Q库了,也就是angular中参考的版本,先安装Q:

npm install q --save

改写一个写入文件的方法:

var Q = require('q');var writeFile= function() {    var deferred = Q.defer();
    fs.writeFile('public/angular/readApp.min.js', uglified.code, function (err) {        if (err) {
            console.log(err);            deferred.reject(err);
        } else {            deferred.resolve('脚本生产并保存成功: readApp.min.js');  }
    });    return deferred.promise;}
writeFile().then(function(res) {
    console.log(res);
});

风格上和jQuery、angular一模一样。但Q的功能很强大,除了我们现在能想到的Q.all(),Q.when(),还提供了Q.any(),Q.delay(),Q.fcall()等方法。大家可以直接去看文档和源码. 

Q库文档:https://github.com/kriskowal/q

Q库的源码:https://github.com/kriskowal/q/blob/v1/design/q7.js

 bluebird

 这个库是从i5ting的博客看到的,这个库强前后端都能用,而且兼容老ie。使用起来很简单。安装:

npm insatll bluebird --save

比如在前端定义一个promise:

function promptPromise(message) {  return new Promise(function(resolve, reject) {    var result = window.prompt(message);    if (result != null) {
      resolve(result);
    } else {
      reject(new Error('User cancelled'));
    }
  });
}

在后端直接promise整个库: 

var Promise = require('bluebird');var fs = Promise.promisifyAll(require("fs"));
fs.readFileAsync("nodemon.json").then(function(json) {
    console.log("Successful json", json);
}).catch(SyntaxError, function (e) {
    console.error("file contains invalid json");
});

调用promisifyAll方法后,fs对象就拥有了readFileAsync方法。 同理可以用到mongoose对象上:

var User = mongoose.model('User', userSchema);Promise.promisifyAll(User);
 
User.findAsync({ username: "stoneniqiu" }).then(function (data) {
    console.log("success!");
}).catch(function(err) {
    console.log(err);
});

相对于我们之前的不断嵌套的写法这样简洁很多了。

甚至夸张的可以promise所有库:

var ParanoidLib = require("...");var throwAwayInstance = ParanoidLib.createInstance();
Promise.promisifyAll(Object.getPrototypeOf(throwAwayInstance));

但这种做法让人有点担心,有兴趣的可以去官网和git上看看。

公式ウェブサイト: http://bluebirdjs.com/docs/why-bluebird.html

github: https://github.com/petkaantonov/bluebird

概要: Node には Promise をサポートするライブラリがたくさんあるので、ここでこれらはほんの数例です。本題に戻り、i5ting の要約を拝借しましょう:

約束の 4 つの重要な点 +

  • 非同期操作の最終結果は、可能な限り独立した操作単位です。

  • Promise で最も重要なこと インタラクティブメソッドは、関数を then メソッド (thenable) に渡すことです

  • 例外キャッチエラーをキャッチします

  • 拒否と解決に基づいてプロセスを再構成します

I以前はpromiseについて漠然と感じていましたが、今日はそれを横に整理しました。これらはすべてJavaScriptコードですが、アプリケーションシナリオと実装方法がまったく異なることが、何と言ってもpromiseの本質であることがより明確になりました。カプセル化が強ければ強いほど、より多くの機能を持ちます。これらの jquery、Angular、node の例から、Promise は非同期プログラミングで広く使用されており、学ぶ価値があることがわかります。この記事がお役に立てば幸いです。不適切な点がございましたら、お気軽にコメントください。

この記事の事例を読んだ後は、この方法を習得したと思います。さらに興味深い情報については、php 中国語 Web サイトの他の関連記事に注目してください。

推奨読書:

H5ビデオ再生ライブラリvideo.jsの詳細な説明

JSの特に使いやすい軽量の日付プラグイン

以上がjQuery、Angular、nodeにおけるPromiseの詳しい解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。