10月阅读周·JavaScript异步编程设计快速响应的网络应用:Promise对象之管道连接未来篇
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读九个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》。
当前阅读周书籍:《JavaScript异步编程设计快速响应的网络应用》。
管道连接未来
在JavaScript中常常无法便捷地执行一系列异步任务,一个主要原因是无法在第一个任务结束之前就向第二个任务附加处理器。举个例子,假设我们要从一个URL抓取数据(GET),接着又将这些数据发送给另一个URL(POST)。
var getPromise = $.get('/query');
getPromise.done(function (data) {
var postPromise = $.post('/search', data);
});
// 现在我们想给postPromise 附加处理器……
看到这里的问题了吗?在GET操作成功之前我们无法对postPromise对象绑定回调,因为这时 postPromise 对象还不存在!除非我们已经得到因$.get调用而异步抓取的数据,否则甚至无法进行那个负责生成postPromise对象的$.post调用!
这正是jQuery 1.6为Promise对象新增pipe(管道)方法的原因。pipe好像在说:“请针对这个 Promise 对象给我一个回调,我会归还一个Promise对象以表示回调运行的结果。”
var getPromise = $.get('/query');
var postPromise = getPromise.pipe(function (data) {
return $.post('/search', data);
});
看起来就像黑魔法,对吧?下面是详情大揭秘:pipe 最多能接受 3个参数,它们对应着Promise对象的3种回调类型:done、fail 和progress。也就是说,我们在上述例子中只提供了执行getPromise时应运行的那个回调。当这个回调返回的Promise对象已经执行/拒绝时,pipe方法返回的那个新Promise对象也就可以执行/拒绝。
从效果上看,pipe就是通向未来的一扇窗户!
我们也可以通过修改 pipe 回调参数来“滤清”Promise对象。如果pipe方法的回调返回值不是Promise/Deferred对象,它就会变成回调参数。举例来说,假设有个Promise对象发出的进度通知表示成0与1之间的某个数,则可以使用pipe方法生成一个完全相同的Promise对象,但它发出的进度通知却转变成可读性更高的字符串。
var promise2 = promise1.pipe(null, null, function (progress) {
return Math.floor(progress * 100) + '% complete';
});
pipe判定参数是否为Promise对象的方法和$.when完全一样:如果pipe的参数带有promise方法,则该方法的返回值会被当作Promise对象以代表调用 pipe 的那个初始 Promise 对象。再重申一次,promise.promise() === promise。
管道级联技术
pipe 方法并不要求提供所有的可能回调。事实上,我们通常只想写成这样:
var pipedPromise = originalPromise.pipe(successCallback);
或是这样:
var pipedPromise = originalPromise.pipe(null, failCallback);
我们能看出初始Promise对象(即originalPromise)在成功/失败之后应触发的回调(第一种情况下因任务成功而触发successCallback,第二种情况下因任务失败而触发 failCallback),所以管道末尾的Promise(即 pipedPromise)行为取决于 successCallback/failCallback的返回值。但是,如果并没有在pipe方法中为初始Promise的任务结果指定回调,又该怎么办呢?
很简单,管道末尾的Promise在这些情况下直接模仿那个初始Promise对象。我们可以这样说:初始Promise对象的行为一直级联到管道末尾的 Promise 对象。这种级联技术非常有用,因为它让我们不费吹灰之力就能定义异步任务的分化逻辑。假设有这样一个分成3步走的进程。
var step1 = $.post('/step1', data1);
var step2 = step1.pipe(function () {
return $.post('/step2', data2);
});
var lastStep = step2.pipe(function () {
return $.post('/step3', data3);
});
这里的 lastStep 对象当且仅当所有这3个Ajax调用都成功完成时才执行,其中任意一个Ajax调用未能成功完成,lastStep均被拒绝。如果只在乎整体进程,则可以省略掉前面的变量声明。
var posting = $.post('/step1', data1)
.pipe(function () {
return $.post('/step2', data2);
})
.pipe(function () {
return $.post('/step3', data3);
});
也可以让后面的pipe嵌套在前面那个pipe的里面。
var posting = $.post('/step1', data1).pipe(function () {
return $.post('/step2', data2).pipe(function () {
return $.post('/step3', data3);
});
});
当然,这会重现金字塔厄运。大家应该了解这种书写风格,不过请尽量逐一声明 pipe 生成的那些Promise对象。也许并不需要这些变量名称,但它们能让代码更加自文档化。
总结
总的说来,pipe的回调可以做以下两件事情。
- 如果pipe回调返回的是Promise对象,则pipe生成的那个Promise对象会模仿这个Promise对象。
- 如果pipe 回调返回的是非Promise对象(值或空白),则pipe 生成的那个Promise对象会立即因该赋值而执行、拒绝或得到通知,具体取决于调用pipe的那个初始Promise对象刚刚发生了什么。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)