前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JavaScript 异步编程指南 — 你不知道的 Promise 前世 Deferred

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

作者头像
一只图雀
发布2021-06-17 20:27:39
9560
发布2021-06-17 20:27:39
举报
文章被收录于专栏:图雀社区图雀社区

这是一个系列文章,你可以关注公众号「五月君」订阅话题《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 书写方式:

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

Jquery 1.5 之后的 ajax 书写方式:

代码语言:javascript
复制
// 返回的是 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() 的方式:

代码语言:javascript
复制
$.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() 方法指定的回调函数。

代码语言:javascript
复制
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() 方法。

代码语言:javascript
复制
const d = wait()
  d.then(() => {
   console.log('success1');
 }, err => {
  console.error('failed1')
 })
 .then(() => {
   console.log('success2');
 }, err => {
  console.error('failed2')
 })

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

代码语言:javascript
复制
do something...
success1
success2

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

代码语言:javascript
复制
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() 会被屏蔽

代码语言:javascript
复制
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
复制
● 字节跳动最爱考的前端面试题:CSS 基础● 字节跳动最爱考的前端面试题:JavaScript 基础● 字节跳动最爱考的前端面试题:计算机网络基础

·END·

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-06-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 图雀社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 了解 Promise 前世 Deferred
  • Ajax 中的 Deferred 对象
  • 封装一个自己的 Deferred 对象
  • 总结
  • Rerefence
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档