您好,欢迎访问一九零五行业门户网

jQuery、Angular、node中的Promise详解

这次给大家带来jquery、angular、node中的promise详解,使用jquery、angular、node中promise的注意事项有哪些,下面就是实战案例,一起来看一下。
最初遇到promise是在jquery中,在jquery1.5版本中引入了deferred object,这个异步队列模块用于实现异步任务和回调函数的解耦。为ajax模块、队列模块、ready事件提供基础功能。在用jquery操作dom的时候对promise的使用欲不够强烈,最近学习node和angular,需要用js写业务逻辑和数据操作代码的时候这种场景需求就出来了。一般来说事件适合在交互场景中运用,因为用户的行为本来就是分散的,而promise这样的流程控制适合在后台逻辑中处理业务。
//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/defferred 。说白了就是为了更好的控制函数执行的流程。回调函数使用简单,但回调使得调用不一致,得不到保证,当依赖于其他回调时,可能会篡改代码的流程,这让调试变得非常难。当写一个插件或者sdk的时候,回调函数一多麻烦就来了;至于事件,自然是要比回调函数干净很多,但事件的场景,类似于通知,a方法执行了,马上触发一个a执行了的事件,订阅者收到通知后马上去干活。事件和回调函数都是被动的,且执行状态没有区分(到底是成功还是失败),要么你就要多写几个回调或多订阅几个事件,结构上和你的流程可能是分离的;因此,如你想在你的代码流程(比如controller/service中)里面调用某个异步方法,想得到确定的结果,那么你需要一个promise(承诺)。这个名字也值得玩味,似乎寓意“说到做到”。
jquery中的promisejquery的异步队列包含jquery.callbacks(flag),jquery.deferred(func)和jquery.when()。而deferred基于callbacks实现,jquery.when()基于前两者。promise是异步队列的一个只读副本。
jquery.callbacks(flag)jquery.callbacks(flag)返回一个链式工具对象,用于管理一组回调函数,内部通过一个数组来保存回调函数,其他方法围绕这个数组进行操作和检测。提供了add()、remove()、firewith/fire()/fired()、disable()/disabled()、lock/locked方法。
callbacks.add() 用于添加一个或一组回调函数到回调函数列表中
callbacks.remove() 用于从回调函数列表中移除一个或一组回调函数
callbacks.firewith(context,args)  使用指定的上下文和参数触发回调函数列表中的所有回调函数
callbacks.fire() 使用指定的参数触发回调函数中的所有回调函数
callbacks.fired()  用于判断函数列表是否被触发过
callbacks.disable() 禁用回调函数列表
callbacks.disabled() 用于判断回调函数是否被禁用
callbacks.lock() 用于锁定回调函数(锁定memory模式下的回调函数的上下文和参数)
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), resolvedreject, fail, jquery.callbacks(once memory), rejectednotify, 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['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中的promiseq 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的总结:
promise/a+的四个要点
异步操作的最终结果,尽可能每一个异步操作都是独立操作单元
与promise最主要的交互方法是通过将函数传入它的then方法(thenable)
捕获异常catch error
根据reject和resolve重塑流程
之前对promise的感觉是影影绰绰,今天横向整理了这篇文章后,清晰了很多了,虽然都是javascript代码,但完全是不同的应用场景和实现方式,不管怎样promise的本质是一样的,封装的越强,功能也越多。从jquery、angular以及node的这些示例中可以看出promise在异步编程中应用的很广泛,值得学习。希望本文对你有帮助,如果有不当之处,欢迎拍砖。
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
h5的视频播放库video.js详解
js里特别好用的轻量级日期插件
以上就是jquery、angular、node中的promise详解的详细内容。
其它类似信息

推荐信息