Promise是一种全新的技术,它可以让你在浏览器中进行异步ajax调用时同步的返回(Return)调用结果。
在此之前,你根本不可能在“不造成网页卡顿”的前提下安全的返回ajax的结果,因为为了返回结果,你必须关闭ajax的异步模式使其进入同步模式。但是,一但你在浏览器用于渲染网页的“主线程”中进入同步模式的ajax,整个标签页除了等待ajax完成就再也做不了任何事了,也就是说整个标签页都会卡住,这对用户体验来说是及其恶劣的。
但是,我们又想返回ajax调用结果,因为我们不想陷入传统的回调函数地狱。所以有了Promise。我会通过一个例子直观的展示什么是Promise。
以下代码可以在网页插件中测试
-------------------------------------
首先是并行两个Ajax请求:
<script src="//hu60.cn/tpl/classic/js/jquery/dist/jquery.min.js"></script>
<script>
//getName函数把虎绿林用户的uid转换为用户名
function getName(uid) {
//Deferred的意思是“延迟”
//这个result虽然会马上返回,但是只有在未来才会得到结果
var result = $.Deferred();
//$.get()返回的也是一个“延迟”对象
//不过这个“延迟”对象是只读的,我们通常称为“Promise”(承诺)
//关于“Promise”的更多细节看return语句的注释
var ajax = $.get('http://hu60.cn/q.php/api.user.json?type=uid2name&uid=' + uid);
//当 result.promise() 被返回的时候,这些代码还没有执行
//只有在ajax请求成功之后,这些代码才会执行
ajax.done(function(json) {
if (!json.error && json.name != null) {
//这是真正“return”结果的地方
//resolve的意思是“解决”
//这是在宣告,我原本给你的“承诺”兑现了,看,json.name就是我想给你的结果
result.resolve(json.name);
} else {
//reject的意思是“背弃”
//这是在宣布,我“背弃”了原本“要给你一个结果”的“承诺”,现在这个“承诺”永远不会有结果了
result.reject();
}
});
//ajax请求失败的时候执行
ajax.fail(function() {
//ajax都没成功,自然要“背弃承诺”
result.reject();
});
//promise()方法得到一个只读的“延迟”对象
//所谓“只读”,就是拿到这个对象的人只能等待结果,不能改变这个对象的状态
//所以我们把只读的“延迟”对象叫做Promise(承诺)
//你可以返回给别人一个“承诺”,告诉他:虽然结果不会马上出来,但是我最后一定会给你一个结果
//而我们改变result对象的状态的时候,它的promise对象的状态也会跟着改变。
return result.promise();
}
//我得到的不是一个用户名,而是一个对用户名的承诺
var result = getName(1);
//承诺兑现了,拿到用户名
result.done(function(name) {
alert('uid(1):' + name);
});
//承诺没有兑现
result.fail(function() {
alert('获取用户名失败');
});
//另一个承诺
var result2 = getName(0);
//兑现了
result2.done(function(name) {
alert('uid(0):' + name);
});
//没有兑现
result2.fail(function() {
alert('获取用户名失败');
});
</script>
在这个例子中,result1会被兑现(得到老虎会游泳),result2会被背弃(因为uid0的name是null)。
你们运行这个代码的时候可能会发现,其实result1和result2的兑现顺序是不固定的。
刷新几次你会发现,有时候“获取用户名失败”先出现,有时候“老虎会游泳”先出现。
我们有理由相信在浏览器中两个ajax请求是同时在执行的。
-------------------------------------
然后是依次执行两个请求,并且在失败的时候返回错误代码:
<script src="//hu60.cn/tpl/classic/js/jquery/dist/jquery.min.js"></script>
<script>
//getName函数把虎绿林用户的uid转换为用户名
function getName(uid) {
//Deferred的意思是“延迟”
//这个result虽然会马上返回,但是只有在未来才会得到结果
var result = $.Deferred();
//$.get()返回的也是一个“延迟”对象
//不过这个“延迟”对象是只读的,我们通常称为“Promise”(承诺)
//关于“Promise”的更多细节看return语句的注释
var ajax = $.get('http://hu60.cn/q.php/api.user.json?type=uid2name&uid=' + uid);
//当 result.promise() 被返回的时候,这些代码还没有执行
//只有在ajax请求成功之后,这些代码才会执行
ajax.done(function(json) {
if (!json.error && json.name != null) {
//这是真正“return”结果的地方
//resolve的意思是“解决”
//这是在宣告,我原本给你的“承诺”兑现了,看,json.name就是我想给你的结果
result.resolve(json.name);
} else {
//reject的意思是“背弃”
//这是在宣布,我“背弃”了原本“要给你一个结果”的“承诺”,现在这个“承诺”永远不会有结果了
result.reject();
}
});
//ajax请求失败的时候执行
ajax.fail(function() {
//ajax都没成功,自然要“背弃承诺”
result.reject();
});
//promise()方法得到一个只读的“延迟”对象
//所谓“只读”,就是拿到这个对象的人只能等待结果,不能改变这个对象的状态
//所以我们把只读的“延迟”对象叫做Promise(承诺)
//你可以返回给别人一个“承诺”,告诉他:虽然结果不会马上出来,但是我最后一定会给你一个结果
//而我们改变result对象的状态的时候,它的promise对象的状态也会跟着改变。
return result.promise();
}
//getName2()获得uid1和uid2的用户名,并且连接成字符串“返回”
//如果请求失败,“返回”错误代码-1或者-2
function getName2() {
var uid1 = 1; //可以改成0,看name1失败的效果
var uid2 = 2; //可以改成0,看name2失败的效果
var result = $.Deferred();
//先获取name1
var name1p = getName(uid1);
//对应同步的风格 if (name1p != null) {...}
name1p.done(function(name1) {
//name1有了,再获取name2
var name2p = getName(uid2);
//对应同步的风格 if (name2p != null) {...}
name2p.done(function(name2) {
//name2有了,现在可以做我们想做的事情了
var nameStr = '1:' + name1 + ', 2:' + name2;
//对应同步的风格 return ...
result.resolve(nameStr);
});
//对应同步的风格 else {...}
name2p.fail(function() {
//对应同步的风格 return -2;
result.resolve(-2);
});
});
//对应同步的风格 else {...}
name1p.fail(function() {
//对应同步的风格 return -1
result.resolve(-1);
});
//这是例行的返回一个承诺
//上面的done和fail里的代码都不会立即运行
//相反,这句代码会立即运行,也就是说,函数马上就返回了
return result.promise();
}
var msgP = getName2();
//对应同步的风格 if (msgP != null) {...}
msgP.done(function(msg) {
if (msg == -2) {
alert('name2失败');
} else if (msg == -1) {
alert('name1失败');
} else {
alert(msg);
}
});
//对应同步的风格 else {...}
msgP.fail(function() {
console.err('失败');
});
//同样,这句代码也会最先执行
alert('Promise是非阻塞的,所以这个提示框是最先出现的');
</script>