当前位置: 首页 > news >正文

JavaScript 异步编程指南——你不知道的Promise前世Deferred

这是一个系列文章,你可以关注公众号「五月君」订阅话题《JavaScript 异步编程指南》获取最新信息。

Promise 是现代 JavaScript 比较重要的一个核心概念,也许你会疑问为什么会提到 Deferred?这个是什么?也许你之前没听过,其实我们现在的 Promise 就是由 Deferred 逐步演变而来形成了如今的一套规范 PromiseA+。

了解 Promise 前世 Deferred

本节你可以跟随笔者一起来了解下这个 Deferred 是什么?对于你以后学习 Promise 我想是会有帮助的,并且对它的历史也会多一些了解、记忆也会更深刻。当今你不能保证所有系统都是使用 React、Vue 来写的,也许你会遇到一些使用 Jquery 写的系统,总不能不维护吧,当你看到它的 Ajax 请求时也知道这个东西是干嘛的,为什么要这样写。

Promise 曾经以多种形式存在于多种语言中,这个词最早由 C++ 工程师用在 Xanadu 项目中,随后被应用于 E 语言中,这又激发了 Python 人员的灵感,将它实现成为了 Twisted 框架的 Deffered 对象。

2007 年 Promise 赶上了 JavaScript 的流行大潮,当时 Twisted 的 Dojo 框架添加了一个名为 dojo.Deferred 对象。当时,相对成熟的 Dojo 在流行方面可以与初出茅庐的 Jquery 相媲美(争夺人气),虽然 Deferred 模式最早出现于 Dojo 代码中,但被广为所知却来源于 Jquery 1.5 版本,这也是 Jquery 中的一个重要的转折点,在这个版本之后引入了一个新的功能 Deferred,它彻底的改变了在 Jquery 中如何使用 Ajax,几乎重写了 Jquery 的 Ajax 部分。

在 2009 年时 Kris Zyp 有感于 dojo.deferred 的影响力,该模式被抽象为一个提议草案,发布在 CommonJS 规范中,后来又抽象出 Promise/A 规范,同年 Node.js 首次亮相。

Node.js 的早期迭代在非阻塞 API 中使用了 Promise。但是,在 2010 年 2 月,Node.js 早期的作者 Ryan Dahl 决定改为现在大家都熟悉的 callback(err, result),理由是 Promise 属于 “用户区” 更高级别构造,所以早期你会看到 Node.js 中的很多 API 都是 callback(err, result) 形式的,包括现在也还有,顺便在说明下 Ryan Dahl 早在 2012 年就已经离开了 Node.js 社区,之后一直由 Node.js 基金会管理,如今已经 2021 年了,Node.js 本身也发生了很多的变化,包括文件操作也为我们提供了基于 Promise 形式的 API,Stream 目前也很好的支持异步迭代,你不用在使用 callback 那种形式嵌套你的程序。

当时 Ryan Dahl 的决定为以 Node.js 为竞争目标的 Promise 实现创建了条件,例如 Q.js 曾一度很流行,是基于 Promise/A 规范相当简单的实现。Futures 是一个更广泛的工具包,其中包含 Async.js 之类的库中提供了许多流程控制功能。

在上一节,我们讲到了在早期我们都是通过使用回调(Callback)的形式向服务器发起网络请求,随后通过注册的回调函数拿到返回的数据,当时我们也提到了基于 Callback 的形式很容易造成回调函数嵌套、错误难以处理,现在我们看下早期 Jquery 中 Deferred 的解决方案是如何做的,与我们后面讲解的 Promise 有什么关联。

Ajax 中的 Deferred 对象

Jquery 1.5 之前的 ajax 书写方式:

// 返回的是 XHR 对象
$.ajax({
  url: "http://openapi.xxxxxx.com/api",
  success: function(){
    console.log("success!");
  },
  error:function(){
    console.log("failed!");
  }
});

Jquery 1.5 之后的 ajax 书写方式:

// 返回的是 Deferred 对象
$.ajax("http://openapi.xxxxxx.com/api")
 .done(function(){ console.log("success1!"); })
 .fail(function(){ console.log("failed1!"); })
 .done(function(){ console.log("success2!"); })
 .fail(function(){ console.log("failed2!"); })

以链式的方式来写,极大的提高了阅读体验,相比回调嵌套确实解决了回调地狱问题,done() 是之前的 success() 方法,fail() 是之前 error() 方法。

了解 Promise 的应该能看出是不是有点感觉像?让我们在改造下,使用 .then() 的方式:

$.ajax("http://openapi.xxxxxx.com/api")
 .then(function(){ console.log("success1!"); }, function(){ console.log("failed1!"); })
 .then(function(){ console.log("success2!"); }, function(){ console.log("failed2!"); })

是不是更像 Promise 了?

封装一个自己的 Deferred 对象

deferred 对象的执行将状态分为三个:未完成、已完成、已失败。调用 dtd.resolve() 是将执行状态变为已完成,会调用 done() 方法指定的回调函数。执行 dtd.reject() 是将执行状态变为已失败,会调用 fail() 方法指定的回调函数。

const wait = () => {
  const dtd = $.Deferred();
  const tasks = () => {
    console.log('do something...')
   dtd.resolve(); // 调用 Deferred 的执行状态为已完成
    // 如果出错也可调用 dtd.reject();
  }
  
  setTimeout(tasks,5000);
  return dtd;
}

现在 wait 返回的就是一个 Deferred 对象了,可以使用链式操作。下面我们使用 dtd.then() 该方法就已经涵盖了 done() 和 fail() 方法。

const d = wait()
  d.then(() => {
   console.log('success1');
 }, err => {
  console.error('failed1')
 })
 .then(() => {
   console.log('success2');
 }, err => {
  console.error('failed2')
 })

运行程序后,大约 5 秒钟我们的程序运行结果如下所示:

do something...
success1
success2

现在还有一个问题,我可以在代码的尾部添加一行 d.resolve(); 这会改变程序的运行结果,这是因为我们在外部改变了执行状态。

const d = wait()
d.then(...); // 和上面一样,此处省略
d.resolve();

// 运行结果
success1
success2
do something...

为了避免这种情况,jQuery 1.5 之后提供了 deferred.promise() 方法,作用是在 deferred 对象上返回 deferred 的 promise 对象,仅能使用与执行状态无关的方法,例如 dtd.then() 或 dtd.done()、dtd.fail() 方法。与执行状态有关的方法 dtd.resolve()、dtd.reject() 会被屏蔽

const wait = () => {
  ...
  return dtd.promise();
}

总结

Deferred 对象有 dtd.resolve()、dtd.reject() 这种与执行状态有关主动触发的函数,也有 dtd.then() 或 dtd.done()、dtd.fail() 这种被动监听的函数,这些函数都在一块,如上面例所示很容易出现在外部被篡改。解决方案是返回一个 dtd.promise() 对象,只能被动监听不能主动修改执行状态。

通过本文你应该会发现这和我们现在使用的 Promise/A+ 这种规范很相似,这也是 Promise/A+ 规范的前世。

Rerefence

  • https://medium.com/pragmatic-programmers/a-very-brief-history-of-promises-fa6cbbb10855

  • http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

推荐阅读

新版犀牛书

《JavaScript权威指南 原书第7版》

 

“犀牛书”已经成为JavaScript程序员心中公认的权威指南。凭着完整的内容、细致的讲解以及海量针对性的示例而受到读者的一致好评,这本巨著主要讲述的内容涵盖JavaScript语言本身,以及Web浏览器所实现的JavaScriptAPI。初学者读完本书,将会对JS有全面的认识,快速掌握JS最核心的技术。而有经验的开发者读完本书,会让你对JS的理解有从量变到质变的深层次飞跃。

如今,全球畅销25年的JS犀牛书全新升级,新版涵盖了ES2020特性,同时删去了已过时的内容。值得珍藏。


扫码关注【华章计算机】视频号

每天来听华章哥讲书

更多精彩回顾

书讯 | 7月书讯(下)| 读书开启下半年

书讯 | 7月书讯(上)| 读书开启下半年

资讯 | 《数据安全法》表决通过!最新解读来了

书单 | 2021半年盘点,不想你错过的重磅新书

干货 | 详解数据资产的8大重要特征

收藏 | 一文了解滴滴与蚂蚁金服开源共建的SQLFlow

上新 | 【新书速递】打通数据科学三要素——数据科学实战性手册

赠书 | 【第64期】豆瓣9.8分,周志明的《凤凰架构》

点击阅读全文购买

相关文章:

  • 《企业破局的34个锦囊》之领导者必备的技术思维
  • Kubernetes诞生日!为什么开发人员应该学习 Kubernetes?
  • SIGIR 2021大奖出炉!Salton奖授予UIUC翟成祥教授
  • AI系统中的偏差与偏见
  • 聊聊Keras的特点及其与其他框架的关系
  • 快收藏!!整理了100个Python小技巧!!
  • Rust跨界前端全攻略
  • 低代码平台的11个能力维度
  • 数学女博士奥运会摘金!用数学知识自己训练,网友:真·学好数理化,走遍天下都不怕...
  • 四步搞定异常SQL
  • 8月书讯(上)| 这些新书不可错过
  • 8月书讯(下)| 这些新书不可错过
  • 【第66期】火山引擎Redis云原生实践
  • 搞数字化转型钱从哪来?遇到阻力怎么办?
  • 软件设计领域没有银弹,但代码大师MaxKanat-Alexander的建议绝对能给你带来启发...
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • php的插入排序,通过双层for循环
  • vue2.0开发聊天程序(四) 完整体验一次Vue开发(下)
  • windows-nginx-https-本地配置
  • 分布式事物理论与实践
  • 工程优化暨babel升级小记
  • 以太坊客户端Geth命令参数详解
  • 用 Swift 编写面向协议的视图
  • 用jQuery怎么做到前后端分离
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • Play Store发现SimBad恶意软件,1.5亿Android用户成受害者 ...
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • # C++之functional库用法整理
  • # Swust 12th acm 邀请赛# [ E ] 01 String [题解]
  • #pragma预处理命令
  • (06)Hive——正则表达式
  • (4) PIVOT 和 UPIVOT 的使用
  • (Git) gitignore基础使用
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (十一)图像的罗伯特梯度锐化
  • (一)Neo4j下载安装以及初次使用
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .net mvc部分视图
  • .NET程序员迈向卓越的必由之路
  • .NET基础篇——反射的奥妙
  • .net快速开发框架源码分享
  • @Bean注解详解
  • @DateTimeFormat 和 @JsonFormat 注解详解
  • @transaction 提交事务_【读源码】剖析TCCTransaction事务提交实现细节
  • [ IO.File ] FileSystemWatcher
  • [Android]Android P(9) WIFI学习笔记 - 扫描 (1)
  • [ASP.NET MVC]如何定制Numeric属性/字段验证消息
  • [C#基础知识]专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
  • [CSAWQual 2019]Web_Unagi ---不会编程的崽
  • [NodeJS]NodeJS基于WebSocket的多用户点对点即时通讯聊天
  • [Python进阶] 识别验证码