ホームページ > 記事 > ウェブフロントエンド > jQuery、Angular、nodeにおけるPromiseの詳しい解説
今回は、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 メソッドを提供します。は、1つまたはコールバック関数のグループをコールバック関数リストに追加するために使用されます | |
は、1つまたはコールバック関数のグループを削除するために使用されますコールバック関数リストから コールバック関数 | |
指定されたコンテキストとパラメータを使用して、コールバック関数リスト内のすべてのコールバック関数をトリガーします | |
コールバック関数をトリガーするために指定されたパラメータ すべてのコールバック関数 | |
は関数リストがトリガーされたかどうかを判断するために使用されます | |
無効化されたコールバック関数リスト | |
コールバック関数が無効かどうかを判断するために使用されます | |
コールバック関数をロックするために使用されます (メモリ モードでコールバック関数のコンテキストとパラメーターをロックします) | |
ロックされているかどうかを判断するために使用されます |
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方法提供了一个或多个对象来执行回调函数的功能,通常是具有异步事件的异步队列。也就是说它可以支持同时给多个耗时的操作添加回调函数。
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中是将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。
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['finally'](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'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('Hello, ' + name + '!'); } else { reject('Greeting ' + name + ' is not allowed.'); } }, 1000); }); };
通知(notify/progress)回调还不支持这种写法。对比看,没太大差别。
function asyncGreet(name) { var deferred = $q.defer(); setTimeout(function() { deferred.notify('About to greet ' + name + '.'); if (okToGreet(name)) { deferred.resolve('Hello, ' + name + '!'); } else { deferred.reject('Greeting ' + name + ' is not allowed.'); } }, 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中有很多耗时的回调写法,比如写入文件,请求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
这个库是从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 サイトの他の関連記事に注目してください。
推奨読書:
以上がjQuery、Angular、nodeにおけるPromiseの詳しい解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。