loadImg('a.jpg', function() {
loadImg('b.jpg', function() {
loadImg('c.jpg', function() {
console.log('all done!');
});
});
});
var resB = B();
var runA = function() {
resB.then(execS1, execS2);
};
runA();
只看上面这行代码,好像看不出什么特别之处。但现实情况可能比这个复杂许多,A要完成一件事,可能要依赖不止B一个人的响应,可能需要同时向多个人询问,当收到所有的应答之后再执行下一步的方案。最终翻译成代码可能像这样:
var resB = B();
var resC = C();
...
var runA = function() {
reqB
.then(resC, execS2)
.then(resD, execS3)
.then(resE, execS4)
...
.then(execS1);
};
runA();
在这里,当每一个被询问者做出不符合预期的应答时都用了不同的处理机制。事实上,Promise规范没有要求这样做,你甚至可以不做任何的处理(即不传入then的第二个参数)或者统一处理。
好了,下面我们来认识下[url=http://promises-aplus.github.io/promises-spec/]Promise/A+规范[/url]:
[list]
[*]一个promise可能有三种状态:等待(pending)、已完成(fulfilled)、已拒绝(rejected)[/*]
[*]一个promise的状态只可能从“等待”转到“完成”态或者“拒绝”态,不能逆向转换,同时“完成”态和“拒绝”态不能相互转换[/*]
[*]promise必须实现[code]then[/code]方法(可以说,then就是promise的核心),而且then必须返回一个promise,同一个promise的then可以调用多次,并且回调的执行顺序跟它们被定义时的顺序一致[/*]
[*]then方法接受两个参数,第一个参数是成功时的回调,在promise由“等待”态转换到“完成”态时调用,另一个是失败时的回调,在promise由“等待”态转换到“拒绝”态时调用。同时,then可以接受另一个promise传入,也接受一个“类then”的对象或方法,即thenable对象。[/*]
[/list]
可以看到,Promise规范的内容并不算多,大家可以试着自己实现以下Promise。
以下是笔者自己在参考许多类Promise库之后简单实现的一个Promise,代码请移步[url=https://github.com/chemdemo/promiseA/blob/master/lib/Promise.js]promiseA[/url]。
简单分析下思路:
构造函数Promise接受一个函数[code]resolver[/code],可以理解为传入一个异步任务,resolver接受两个参数,一个是成功时的回调,一个是失败时的回调,这两参数和通过then传入的参数是对等的。
其次是then的实现,由于Promise要求then必须返回一个promise,所以在then调用的时候会新生成一个promise,挂在当前promise的[code]_next[/code]上,同一个promise多次调用都只会返回之前生成的[code]_next[/code]。
由于then方法接受的两个参数都是可选的,而且类型也没限制,可以是函数,也可以是一个具体的值,还可以是另一个promise。下面是then的具体实现:
Promise.prototype.then = function(resolve, reject) {
var next = this._next || (this._next = Promise());
var status = this.status;
var x;
if('pending' === status) {
isFn(resolve) && this._resolves.push(resolve);
isFn(reject) && this._rejects.push(reject);
return next;
}
if('resolved' === status) {
if(!isFn(resolve)) {
next.resolve(resolve);
} else {
try {
x = resolve(this.value);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;
}
if('rejected' === status) {
if(!isFn(reject)) {
next.reject(reject);
} else {
try {
x = reject(this.reason);
resolveX(next, x);
} catch(e) {
this.reject(e);
}
}
return next;
}
};
这里,then做了简化,其他promise类库的实现比这个要复杂得多,同时功能也更多,比如还有第三个参数——notify,表示promise当前的进度,这在设计文件上传等时很有用。对then的各种参数的处理是最复杂的部分,有兴趣的同学可以参看其他类Promise库的实现。
在then的基础上,应该还需要至少两个方法,分别是完成promise的状态从pending到resolved或rejected的转换,同时执行相应的回调队列,即[code]resolve()[/code]和[code]reject()[/code]方法。
到此,一个简单的promise就设计完成了,下面简单实现下两个promise化的函数:
function sleep(ms) {
return function(v) {
var p = Promise();
setTimeout(function() {
p.resolve(v);
}, ms);
return p;
};
};
function getImg(url) {
var p = Promise();
var img = new Image();
img.onload = function() {
p.resolve(this);
};
img.onerror = function(err) {
p.reject(err);
};
img.url = url;
return p;
};
由于Promise构造函数接受一个异步任务作为参数,所以[code]getImg[/code]还可以这样调用:
function getImg(url) {
return Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve(this);
};
img.onerror = function(err) {
reject(err);
};
img.url = url;
});
};
接下来(见证奇迹的时刻),假设有一个BT的需求要这么实现:异步获取一个[url=http://chemdemo.github.io/demos/promise/map.json]json配置[/url],解析json数据拿到里边的图片,然后按顺序队列加载图片,没张图片加载时给个loading效果
function addImg(img) {
$('#list').find('> li:last-child').html('').append(img);
};
function prepend() {
$('<li>')
.html('loading...')
.appendTo($('#list'));
};
function run() {
$('#done').hide();
getData('map.json')
.then(function(data) {
$('h4').html(data.name);
return data.list.reduce(function(promise, item) {
return promise
.then(prepend)
.then(sleep(1000))
.then(function() {
return getImg(item.url);
})
.then(addImg);
}, Promise.resolve());
})
.then(sleep(300))
.then(function() {
$('#done').show();
});
};
$('#run').on('click', run);
这里的sleep只是为了看效果加的,可猛击查看[url=http://chemdemo.github.io/demos/promise/browser.html]demo[/url]!当然,Node.js的例子可查看[url=https://github.com/chemdemo/promiseA/blob/master/test/nodejs.js]这里[/url]。
在这里,[code]Promise.resolve(v)[/code]静态方法只是简单返回一个以v为肯定结果的promise,v可不传入,也可以是一个函数或者是一个包含[code]then[/code]方法的对象或函数(即thenable)。
类似的静态方法还有[code]Promise.cast(promise)[/code],生成一个以promise为肯定结果的promise;
[code]Promise.reject(reason)[/code],生成一个以reason为否定结果的promise。
我们实际的使用场景可能很复杂,往往需要多个异步的任务穿插执行,并行或者串行同在。这时候,可以对Promise进行各种扩展,比如实现[code]Promise.all()[/code],接受promises队列并等待他们完成再继续,再比如[code]Promise.any()[/code],promises队列中有任何一个处于完成态时即触发下一步操作。
[h2][url=https://github.com/chemdemo/chemdemo.github.io/blob/master/blogs/promise.md#%E6%A0%87%E5%87%86%E7%9A%84promise][/url]标准的Promise[/h2]
可参考html5rocks的这篇文章[url=http://www.html5rocks.com/en/tutorials/es6/promises/]JavaScript Promises[/url],目前高级浏览器如chrome、firefox都已经内置了Promise对象,提供更多的操作接口,比如[code]Promise.all()[/code],支持传入一个promises数组,当所有promises都完成时执行then,还有就是更加友好强大的异常捕获,应对日常的异步编程,应该足够了。
[h2][url=https://github.com/chemdemo/chemdemo.github.io/blob/master/blogs/promise.md#%E7%AC%AC%E4%B8%89%E6%96%B9%E5%BA%93%E7%9A%84promise][/url]第三方库的Promise[/h2]
现今流行的各大js库,几乎都不同程度的实现了Promise,如dojo,jQuery、Zepto、when.js、Q等,只是暴露出来的大都是[code]Deferred[/code]对象,以jQuery(Zepto类似)为例,实现上面的[code]getImg()[/code]:
function getImg(url) {
var def = $.Deferred();
var img = new Image();
img.onload = function() {
def.resolve(this);
};
img.onerror = function(err) {
def.reject(err);
};
img.src = url;
return def.promise();
};
当然,jQuery中,很多的操作都返回的是Deferred或promise,如[code]animate[/code]、[code]ajax[/code]:
// animate
$('.box')
.animate({'opacity': 0}, 1000)
.promise()
.then(function() {
console.log('done');
});
// ajax
$.ajax(options).then(success, fail);
$.ajax(options).done(success).fail(fail);
// ajax queue
$.when($.ajax(options1), $.ajax(options2))
.then(function() {
console.log('all done.');
}, function() {
console.error('There something wrong.');
});
jQuery还实现了[code]done()[/code]和[code]fail()[/code]方法,其实都是then方法的shortcut。
处理promises队列,jQuery实现的是[code]$.when()[/code]方法,用法和[code]Promise.all()[/code]类似。
其他类库,这里值得一提的是[url=https://github.com/cujojs/when]when.js[/url],本身代码不多,完整实现Promise,同时支持browser和Node.js,而且提供更加丰富的API,是个不错的选择。这里限于篇幅,不再展开。
[h2][url=https://github.com/chemdemo/chemdemo.github.io/blob/master/blogs/promise.md#%E5%B0%BE%E5%A3%B0][/url]尾声[/h2]
我们看到,不管Promise实现怎么复杂,但是它的用法却很简单,组织的代码很清晰,从此不用再受callback的折磨了。
最后,Promise是如此的优雅!但Promise也只是解决了回调的深层嵌套的问题,真正简化JavaScript异步编程的还是Generator,在Node.js端,建议考虑Generator。
下一篇,研究下Generator。
github原文: [url=https://github.com/chemdemo/chemdemo.github.io/issues/6]https://github.com/chemdemo/chemdemo.github.io/issues/6[/url]
机械节能产品生产企业官网模板...
大气智能家居家具装修装饰类企业通用网站模板...
礼品公司网站模板
宽屏简约大气婚纱摄影影楼模板...
蓝白WAP手机综合医院类整站源码(独立后台)...苏ICP备2024110244号-2 苏公网安备32050702011978号 增值电信业务经营许可证编号:苏B2-20251499 | Copyright 2018 - 2025 源码网商城 (www.ymwmall.com) 版权所有