Promise/promise deferred模式式是怎么工作的

Promise原理解析与实现
之前写过一篇I promise, I resolve, 但我连微博都没发, 因为我自己都觉得很扯淡, 现在看来果然是在扯淡
这里说的Promise是es6 harmony的Promise, 而非那个DOM Promise.
现在的Chrome两种Promise都支持, 但默认为DOM的Promise, 要想打开harmony模式, 还得要在chrome://flag中打开harmony (启用实验性 JavaScript)
分辨dom promise和harmony promise的方法就是在dev中输入Promise(function(){})
如果报错了说明是dom的promise, 不报错则为harmony的promise
因为dom的promise标准已经被删除, 而harmony的promise既可以在浏览器中用又可以将来在nodejs中用, 我们当然是选harmony的promise啦
现在我们来尝试用100行左右代码实现一下promise的大概功能
首先写出主要的Promise函数
function Promise(resolver) {
resolver(resove, reject)
我们都知道Promise的参数是一个函数, 其参数是promise内部控制流程的resolve和reject
看到这里, 想必大家觉得很熟悉, 所有流控制的库貌似都是传一个表达 继续往下传 的内部函数, 说大白话就是 我这里搞定了, 你继续 的回调函数
比如express4之前用到的, 其中的app.*()中的function第三个参数就是next, 可以用来移至下一个路由栈继续匹配, 而promise则使用了两个内部函数, 一个表达流程正确的resolve(解决了), 另一个是流程失败的reject(拒绝了)
虽然外观略不同, 但不管是connect还是promise, 其内部都有一个stack或者queue的东西保存着全部的流, 在js中显然也就是一个数组
比如express中可以这么链式的写
app.use(function(req, res, next) {
}).get('/xxx', function(req, res, next) {
}).use(function() {
其整个路由栈都被存入一个数组, 在next的时候移到下一个
而Promise的链式用法则为
// 先封装一个返回promise的函数
function delay(time) {
return new Promise(function(resolve) {
setTimeout(resolve, time)
delay(100).then(function() {
return delay(200)
}).then(function() {
return delay(300)
promise的链式由then中的resolve返回值加入, 而非一开始就全部塞入, 这就是promise和express中next的主要区别
继续试着实现promise
function Promise(resolver) {
resolver(resolve, reject)
var queue = [] // 保存链式调用的数组
this.then = function(resolve, reject) {
queue.push([resolve, reject]) // 把then中的resolve和reject都存起来
我们还没有写resolve和reject这两个内部函数呢, 这俩函数作用完全一样, 只不过一个表示正确, 一个表示错误, 我们完全可以用一个类似connect中的next来表达这两个函数
function resolve(x) {
next(0, x) // 用0来告诉next是resolve
function reject(resson) {
next(1, reason) // 用1告诉next是reject
那这个控制流程的next到底该啥样呢, 我们都知道, next的作用不过是去调用queue中下一个函数而已
function next(i, val) {
// i 仅仅是用来区分resolve还是reject, val是值
while (queue.length) {
var arr = queue.shift() // 移出一个resolve和reject对, 也就是[resolve, reject]
arr[i](val) // 执行之, val是唯一参数
为何要while呢? 因为promise可以不停的then下去, 只不过传下来的都是resolve中的值都是undefined罢了, 因此我们用while来用光全部的resolve
问题就来了, 如果我们这么写
var p = new Promise(function(resolve) {
resolve('ok')
p.then(function(x) {
console.log(x)
因为完全没有延迟, 显然resolve先走了, 而resolve执行的时候, queue中还没有函数去接它, 这个时候就then就不可能触发了
因此要么把resolve的值存起来, 要么就是让resolve肯定晚于后面的then执行
我这里偷一下懒, 用一下setTimeout
function(i, val) {
setTimeout(function() {
while (queue.length) {
var arr = queue.shift()
arr[i](val)
这样resolve的出栈动作就肯定比进栈晚了, 不过这样写虽然很简洁, 但肯定有隐患(只不过我还没发现)
那如何让Promise支持链式调用呢? 这也不难, 我们只需将其执行结果存起来, 帮它then下去即可
function next(i, val) {
setTimeout(function() {
while (queue.length) {
var arr = queue.shift()
if (typeof arr[i] === 'function') {
var chain = arr[i](val)
} catch (e) {
return reject(e)
if (chain && typeof chain.then === 'function') {
// 一般来说链式的话resolve返回值为一个promise对象
// 所谓promise对象, 其实不过是 {then: function() {} }
// 也就是一个含有then函数的对象
return chain.then(resolve, reject)
// 注意, 此处resolve中同样可以返回一个普通值
// 我们帮他包装成promise对象即可
return Promise.resolved(chain).then(resolve, reject)
上面是一个加上错误处理的next函数, 错误处理在promise中, 就是转成reject即可
Promise还有其它函数, 比如Promise.all, Promise.resolved等
我至今都不知道是Promise.resolved还是Promise.resolve
不过我觉得resolved听上去更对一点 (一个已经解决的承诺) , 而且chrome中也是这样的
实现这些附属函数特别简单
Promise.resolved = Promise.cast = function(x) {
return new Promise(function(resolve) {
resolve(x)
Promise.rejected = function(reason) {
return new Promise(function(resolve, reject) {
reject(reason)
Promise.all = function(values) {
var defer = Promise.deferred()
var len = values.length
var results = []
values.forEach(function(p, i) {
p.then(function(x) {
results[i] = x
if (len === 0) {
defer.resolve(results)
}, function(r) {
defer.reject(r)
return defer.promise
这里的all用到了一个Promise.deferred的函数, 这个函数格外重要
Promise.deferred
deferred的实现同样不难, 但其使用概率则是大大的, 可能比直接用Promise的几率还大
Promise.deferred = function() {
var result = {}
result.promise = new Promise(function(resolve, reject) {
result.resolve = resolve
result.reject = reject
return result
deferred的使用方法非常顺手
var def = Promise.deferred()
setTimeout(function() {
def.resolve(222)
def.promise.then(function(x) {
console.log(x)
看到def, 才能看到Promise的精髓, 甚至jQuery反而提供defer作为主对象, promise不过是附属对象
我的完整Promise在
虽然目前Promise还不到100行, 但真正实现起来, 要比那样借助yield的异步框架混淆很多, 我已经改了很多次, 但仍有bug, 这当然也跟我最近老打飞机有关, 已经有点神志不清
但是yield估计几年后才能用, 因此趁早学会Promise还是有必要滴
Copyright (C) 2014jQuery中deferred对象使用方法详解
投稿:lijiao
字体:[ ] 类型:转载 时间:
这篇文章主要为大家详细介绍了jQuery中deferred对象使用方法,也就是延迟对象的使用方法,感兴趣的小伙伴们可以参考一下
在jquery1.5之后的版本中,加入了一个deferred对象,也就是延迟对象,用来处理未来某一时间点发生的回调函数。同时,还改写了ajax方法,现在的ajax方法返回的是一个deferred对象。
那就来看看deferred对象的用法。
1.ajax的链式回调&
// ajax方法返回的是一个deferred对象,可以直接使用链式写法
$.ajax('test.json').done(function(resp){
// done 相当于success回调,其中默认的参数为success回调的参数
alert('success');
}).fail(function(){
// fail 相当于error回调
alert('error');
还可以同时写多个回调,会按照顺序依次执行&
$.ajax('test.json').done(function(resp){
// done 相当于success回调,其中默认的参数为success回调的参数
alert('success');
}).done(function(){
// do something...
}).done(function(){
// do something...
deferred对象还有一个then方法,其实它是一个整合done和fail的方法,它接受一到两个参数,如果有两个参数,那么第一个就是done方法的回调函数,第二个是fail方法的回调函数。如果只有一个参数,那就是done方法的回调函数。&
var success = function(){
alert('success');
var error = function(){
alert('error');
// 两个参数
$.ajax('test.json').then(success, error);
// 一个参数
$.ajax('test.json').then(success);
jQuery还提供了一个$.when(deferreds)的方法来执行一个或多个延迟对象的回调函数,当它的参数是延迟对象时,它会在所有延迟对象代表的异步执行完后再执行相应的回调函数&
$.when($.ajax('test.json'), $.ajax('demo.json')) .done(function(){
alert('success');
}).fail(function(){
alert('error');
很好理解,只有当所有异步都成功时,才会执行done方法中的回调,否则会执行fail方法中的回调,同样好理解的是的done方法中回调函数的默认参数数量则和when方法参数数量相同。
而如果when方法中传入的只是普通对象,不是deferred对象时,会立即执行done方法中的回调,回调函数的默认参数为传入when方法的对象本身。
// 当传入when方法的参数只是普通对象时
$.when({test: 'test'}).done(function(resp){
console.log(resp.test); // 'test'
}).fail(function(){
// 由于传入的对象不是deferred对象,那么就不会调用fail中的回调了
当你需要两个甚至更多的异步结束后才调用回调函数,同时这些异步ajax可能还需要修改传输方式type或者传数据data时,代码就显得很乱,可读性很差。&
所以就可以对ajax进行再次封装,提高代码可读性&
var ajax = function(url, type, param){
return $.ajax({
type: type,
data: param || {}
ajax('test.json').done(function(resp){
alert('success');
}).fail(function(){
alert('error');
接者学习,漏了一个always()方法,参数也是回调函数,与done和fail不同的是,无论任何情况都执行always方法中的回调。
deferred对象不光可以用在jquery的ajax方法中,他提供了一系列的接口,使它的通用型大大提高。
比如有这样一个耗时比较久的方法
function a(){
function b(){
alert('start');
setTimeout(b, 3000);
如果要在这个方法之后执行某个回调,就不能用$.when()了,因为当$.when()的参数不为deferred对象是会直接调用done或者always中的回调函数。
这个时候就要使用deferred对象的其他方法了,还是上面的方法,做一些改写&
function a(){
var def = $.Deferred(); // 创建deferred对象
function b(){
alert('start');
def.resolve(); // 改变deferred对象的状态
setTimeout(b, 3000);
$.when(a()).done(function(){
alert("It's callback");
分析一下:
&1). $.Deferred()方法会创建一个deferred对象
&2). def.resolve()会改变deferred对象的状态,deferred对象有三种状态,未完成,成功,失败。
&它有resolve()和reject()两个方法,resolve方法可以把对象状态改为成功,reject方法可以把状态改为失败。
&又有以上的写法会出现问题,返回的deferred对象可以被外部改变状态,所以还提供了一个promise()方法,这个方法会在deferred对象的基础上返回一个新的deferred对象,不同的是,返回的对象只存在可被观察到状态,而不具备可改变其状态的方法,类似返回了一个只读的deferred对象。
&所以同样的例子可以改写成这样&
function a(){
var def = $.Deferred(); // 创建deferred对象
function b(){
alert('start');
def.resolve(); // 改变deferred对象的状态
setTimeout(b, 3000);
return def.promise();
$.when(a().reject()).done(function(){ // reject()方法无效
alert("It's callback");
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具

我要回帖

更多关于 js promise deferred 的文章

 

随机推荐