Home > Article > Web Front-end > Detailed explanation of Promise in jQuery, Angular and node
This time I will bring you a detailed explanation of Promise in jQuery, Angular, and node. What are the precautions when using Promise in jQuery, Angular, and node. The following is a practical case, let's take a look.
I first encountered Promise in jQuery. Deferred Object was introduced in jQuery1.5. This asynchronous queue module is used to decouple asynchronous tasks and callback functions. Provide basic functions for the ajax module, queue module, and ready event. When using jQuery to operate DOM, I don't have a strong desire to use Promise. Recently, I have been learning node and Angular and need to use js to write business logic and data operation code. This kind of scenario needs come out. Generally speaking, events are suitable for use in interactive scenarios, because user behavior is inherently dispersed, and process control like promises are suitable for processing business in background logic.
//jQuery1.5之前 $.ajax({ url: 'url', success: function(data, status, xhr) {}, error: function(xhr, status, msg) {}, complete: function(xhr, status) {} });
The recommended writing method now is:
$.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) { });
What is the difference? It seems that the former uses a callback function, and the latter is a chain syntax. Can be called repeatedly. The execution of JavaScript is single-threaded and asynchronous. There are generally three ways to handle asynchronous programming: callback functions, event publishing/subscription, and Promise/Defferred. To put it bluntly, it isto better control the process of function execution. The callback function is simple to use, but the callback makes the call inconsistent and cannot be guaranteed. When relying on other callbacks, the flow of the code may be tampered with, which makes debugging very difficult. When writing a plug-in or SDK, trouble comes when the callback function is too large; as for the event, it is naturally much cleaner than the callback function, but the event scenario is similar to the notification. When the A method is executed, an A execution is triggered immediately. When the event occurs, the subscriber will go to work immediately after receiving the notification. Events and callback functions are both passive, and there is no distinction between execution status (success or failure). Either you have to write more callbacks or subscribe to more events, which may be structurally separate from your process; therefore , if you want to call an asynchronous method in your code flow (such as controller/service) and get a certain result, then you need a Promise. This name is also worth pondering, as it seems to mean "do what you say".
jquery’s asynchronous queue includes jQuery.Callbacks(flag), jQuery.Deferred(func) and jQuery.when(). Deferred is implemented based on callbacks, and jquery.when() is based on the first two. Promise is a read-only copy of the asynchronous queue.
jQuery.Callbacks(flag) returns a chain tool object for managing a set of callback functions. The callback functions are saved internally through an array. Other methods Operate and inspect around this array. Provides add(), remove(), fireWith/fire()/fired(), disable()/disabled(), lock/locked methods.
callbacks.add() | Used to add one or a group of callback functions to the callback function list |
callbacks.remove() | Used to remove one or a group of callback functions from the callback function list |
Use the specified context and parameters to trigger all callback functions in the callback function list | |
Use the specified parameters to trigger the callback function All callback functions | |
is used to determine whether the function list has been triggered | |
Disabled callback function list | |
Used to determine whether the callback function is disabled | |
Used to lock the callback function (lock the context and parameters of the callback function in memory mode) | |
Used to determine whether it is locked |
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上看看。
Official website: http://bluebirdjs.com/docs/why-bluebird.html
github:https://github.com/petkaantonov/bluebird
Summary: Supported in node There are many promise libraries, so I won’t list them one by one. Let’s go back to the topic and borrow i5ting’s summary:
Four key points of promise/a+
The end of asynchronous operations As a result, every asynchronous operation is an independent unit of operation as much as possible
The main way to interact with Promise is by passing the function into its then method (thenable)
Catch exception catch error
Reshape the process based on reject and resolve
My previous feeling about promises was vague. After sorting out this article horizontally today, it became much clearer. Although they are all JavaScript codes, they are completely different application scenarios and implementation methods. No matter what, the essence of promise is the same. The stronger the encapsulation, the more functions it has. From these examples of jquery, Angular and node, we can see that promises are widely used in asynchronous programming and are worth learning. I hope this article is helpful to you. If there is anything inappropriate, please feel free to comment.
I believe you have mastered the method after reading the case in this article. For more exciting information, please pay attention to other related articles on the php Chinese website!
Recommended reading:
The above is the detailed content of Detailed explanation of Promise in jQuery, Angular and node. For more information, please follow other related articles on the PHP Chinese website!