标签: Promise


在ES6之前的JavaScript中处理异步的方法就是使用回调函数,当我们不知道一件事情会在什么时候结束,但是又希望在这件事情结束之后再去做一下其他的操作时,我们在这件事情执行之前就先规定好调用事情执行完之后再执行的操作,这也就是所谓的回调,在执行函数之前先告诉这个函数执行完之后下一步需要做什么。

回调函数在异步操作相对较少的情况下使用起来确实不会有太大的问题,但是如果异步操作增多时,由于异步的不可预知性,那么整个代码的可读性就会比较差,因为无法清楚的知道代码实际的运行情况,同时多个异步之间的顺序性也是无法保证的。这也就是使用回调表达程序和管理并发的两个主要缺陷:缺乏顺序性和可信任性。

什么是Promise

关于promise的api在理解上并没有特别的困难,但是我们在学习一个新的工具的时候还是应该先理解其中更深层次的原理与概念,先从这一步来解释什么是promise,才能够更好的是使用promise。

未来值

以一个我们日常在饭店吃饭的例子来类比就是:当我们去一个快餐店吃饭,点了一个青椒炒肉,给收银员付了15元,很明显饭店的菜都是现炒的,我们不可能马上就拿到我们的午饭,这个时候收银员给了我一个小票,当我的青椒炒肉做好的时候,我可以凭借这个小票拿到它,这个小票就是我能够拿到午饭的依据,店员承诺(promise)拿着这个小票可以取到一份青椒炒肉。

只要我好好的保存这个小票,我就不用担心吃不上午饭了,因为它代表了我未来的午饭。

当然在这个等待的过程中,我还可以去做一些其他的事情,比如刷一下知乎。

尽管我现在还没有拿到我的午饭,但是我有了这个小票,它相当于我的午饭的占位符,从本质上来讲,这个占位符使得这个值不再依赖时间。这是一个未来值。

当服务员叫到我的订单号时,我拿着我的小票就能够换回我的午餐——青椒炒肉,也就是说只要我的午餐已经炒好了,那么我就能够用店员当初给我的承诺(小票)来换取这个值本身。

当然也还有这么一种情况,店里面现在已经没有肉了,当我拿着小票去找店员拿我的午餐的时候,店员告诉我,做不了青椒炒肉了。从这里也就能够看得出来,我所需要的未来值可能失败也可能成功。

也就是我每次去吃饭的时候,最终要么得到一份青椒炒肉,要么到的一个肉已经卖完了的消息。

当然在代码中事情并不会这么简单,有可能我的订单被店员忘记了,那么我的订单将永远都不会被叫到,在这种情况下,我就永远处于一个未决议的状态。

  1. 现在值与未来值

下面让我们使用代码来描述一下上面的情况,但是在具体解释promise之前,先使用比较好理解一点的方式——回调来解释一下未来值。

当我们在写代码使用某一个变量的值的时候,实际上我们已经对这个值做了一个非常基本的假设,那就是他应该是我们已经确定他是一个具体的现在值了:

let x,y = 2
console.log(x + y)// NaN <-- 因为x还没有设定值
复制代码

在我们执行x+y的时候,我们假定了x和y都是一个具体的值了,用术语来说就是x和y的值都是已决议的。

从上面的代码能够发现,我们在执行+运算的时候x的值其实并没有确定,而这个运算是无法检测x和y的值是否已经决议好,它也无法等待x和y都决议好之后再执行运算。如果在代码中有个语句现在完成,而有的语句是将来才完成,那么就会引起程序的混乱。

如果两条语句中任意一个可能还没有完成,我们改如何追踪者两条语句的关系能?如果语句2依赖语句1的完成,那么就只有两种情况:语句1马上完成,一切顺利进行,要么语句1没有完成,而语句2也会因此失败。

那么我们应该如何保证在运行x+y这个运算时是安全的呢?其实也就是在执行语句的时候要保证x和y的值都已经准备好了。换一种表述方式就是:“把x和y加起来,但是如果他们其中的任何一个还没有准备好,就等待两者都准备好。一旦可以立马执行加运算。”

首先使用回调的形式:

function add(getX, getY, cb) {var x,ygetX(function (xVal) {x = xValif(y !== undefined) {cb(x + y)}})getY(function (yVal) {y = yValif(x !== undefined) {cb(x + y)}})
}
// fetchX() 和fetchY()是同步或者异步函数
add(fetchX, fetchY, function (sum) {console.log(sum)
})
复制代码

虽然我们为了执行一个x+y写了比较多的代码,但是在这段代码中我们把x和y当做未来值,并且表达了一个add()运算,这个运算并不会在意x和y现在是否可用。它把现在和将来归一化了,因此我们可以保证add()运算的输出是可预测的。

说得更加直白一点就是,未来统一处理现在和将来,我们把它们都变成了将来,即所有的操作都变成了异步。

  1. Promise值

下面先来看一下如何使用promise函数来表达这个x+y的例子:

function add(xPromise, yPromise) {return Promise.all([xPromise, yPromise]).then(function (values) {return values[0] + values[1]})
}
add(fetchX(), fetchY()).then(function (sum) {console.log(sum)
})
复制代码

直接掉调用fetchX(), fetchY(),它们的返回值被传递给add()。这些promise代表的底层值的可用的时间可能是现在,也可能是未来,但是不管怎么样,promise归一保证了行为的一致性。我们可以按照不依赖时间的方式追踪x和y。它们是未来值。

当然就像去吃午饭一样,promise的决议结果可能是拒绝也可能是完成。拒绝值和完成的promise不一样:完成值是通过我们代码最终得到一个我们期望的值,而拒绝值通常是一个拒绝原因。可能是程序的逻辑直接设置的,也可能是从运行异常隐式得到的值。

通过promise,调用then()实际上可以接受两个函数,第一个用于完成情况,第二用于拒绝情况。

add(fetchX(), fetchY()).then(function (sum) {console.log(sum)
}, function (err) {console.log(err)
})
复制代码

从外部来看,由于Promise封装了依赖于时间的状态——等待底层的值完成或拒绝,所以Promise本身是与时间无关的。因此Promise可以按照可预测的方式组成,而不用关心时序或底层结果。

Promise决议之后就是外部不可变的值,我们可以安全的把这个值传递给第三方,并确信它不会被修改。如果有多方同时查看同一个Promise决议时,任何一方都无法影响到另一方对Promise决议的观察结果。Promise决议之后的不可变性是Promise设计中最基础和最重要的因素!

完成事件

Promise的决议:一种在异步任务中作为两个或更多步骤的流程控制机制,时序上的this-then-that。

假如现在有一个异步任务foo,我们不知道也不关心它的任何细节,这个函数可能立即完成,也可能需要一段时间才能完成。

我们只需要知道foo在什么时候结束,这样就可以继续继续下一个任务。换句话来说我们想要通过某种方式在foo完成的时候通知我们一下,以便可以继续下一步任务。

在JavaScript中,如果需要侦听某一个通知,可能第一反应就是事件。所以可以把对通知的需求重新组织为对foo发出一个完成事件的监听。

使用回调的话,通知就是任务调用的回调。而使用Promise的话,就把这个关系反转了过来,侦听来自foo的事件,然后得到通知的时候,根据情况进行下一步的操作。

首先,根据上面的需求我们可以得到下面的伪代码:

foo(x) {// 做一些异步操作
}
foo(88)
on(foo "completion") {// foo完成开始下一步
}
on(foo “error”) {// 出错了
}
复制代码

根据事件的特点,我们调用foo()然后建立两个事件侦听器,一个用于foo完成,一个用于foo出错,从本质上来说foo()并不需要了解调用代码订阅了那些事件,这样可以很好的实现关注点分离。

当然原生的JavaScript并没有提供这样的东西。下面是在JavaScript中更加自然的表达方式。

function foo(x) {// 做一些异步操作// 构造一个listener事件通知处理对象来返回return listener
}
let evt = foo(42)
evt.on('completion', function () {// foo完成开始下一步
})
evt.on('error', function () {// 出错了
})
复制代码

foo()显式创建并返回了一个事件订阅对象,调用代码得到这个对象,并且在其上注册了两个事件处理函数。

相对于我们比较熟悉的面向回调来说,这里没有把回调传递给foo(),而是返回一个名为evt的事件注册对象,由它来接受回调,将控制返还给调用代码。

还有一个重要的好处就是可以把这个事件侦听对象提供给代码中多个独立的部分,在foo()完成的时候,它们都可以独立的得到通知,然后执行下一步。

var evt = foo(52)
bar(evt)
baz(evt)
复制代码

很明显使用上面的操作实现了更好的关注点分离,其中bar()和baz()不需要牵扯到foo()的调用细节。foo()也不需要知道或者关注bar()和baz()是否存在。

从本质上来说,evt对象就是分离的关注点之间一个中立的第三方协商机制。

Promise“事件”

很明显,我们前面说到的事件侦听对象evt就是Promise的一个模拟。

在基于Promise的方法中,前面的代码片段会让foo()创建并返回一个Promise实例,而且这个Promise会被传递到bar()和baz()。

你可能会猜测bar()和baz()的内部实现或许如下:

function bar(fooPromise) {fooPromise.then(function () {// foo执行完毕,执行bar},function () {// 出错了})
}
复制代码

Promise决议也不一定要像前面将Promise作为未来值查看时一样会涉及发送消息。它也可以只作为一种流程控制信号。

另一种实现方式是:

function bar() {// foo执行完毕,执行bar
}
function baz() {// foo执行完毕,执行baz
}
function oopsBar() {// 出错了
}
var p = foo(42)
p.then(bar, oopsBar)
p.then(baz, oopsBar)
复制代码

作者简介:李成文,芦苇科技web前端开发工程师,擅长网站建设、微信公众号开发、微信小程序开发、小游戏制作、企业微信制作、H5建设,专注于前端框架、交互设计、图像绘制、数据分析等研究。

个人博客:LCW blog

欢迎和我们一起并肩作战: web@talkmoney.cn 访问 www.talkmoney.cn 了解更多

提供深圳微信公众号制作,广东钉钉开发,专业的企业微信外包,高性价比的微信小程序建设,靠谱的小游戏制作,高质量的H5开发

Promise从入门到精通相关推荐

  1. 【Vue学习笔记】尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通

    尚硅谷Vue2.0+Vue3.0全套教程丨vue.js从入门到精通 1.Vue核心部分 1.1 Vue简介 1.1.1 Vue是什么? Vue是一套用于构建用户界面的渐进式JavaScript框架. ...

  2. 【Promise】入门-同步回调-异步回调-JS中的异常error处理-Promis的理解和使用-基本使用-链式调用-七个关键问题

    文章目录 1. 预备知识 1.1 实例对象与函数对象 1.2 两种类型的回调函数 1. 同步回调 2. 异步回调 1.3 JS中的异常error处理 1. 错误的类型 2. 错误处理(捕获与抛出) 3 ...

  3. 前端-Vue.js从入门到精通基础笔记(理论+实操+知识点速查)

    #[2022.3]尚硅谷Vue.js从入门到精通基础笔记(理论+实操+知识点速查) 前言 本文完全基于 参考资料:加工整理而成,包括其代码,案例,资源等.前置知识是学习尚硅谷的视频教程,本文配合其教程 ...

  4. 1. Vue从入门到精通(第一章 vue核心)

    Vue从入门到精通(第一章 vue核心) 第一章 Vue核心 1. Vue简介 1.1 Vue是什么? 1.2 Vue的作者以及迭代版本 1.3 Vue的特点 2. 搭建Vue开发环境 2.1 安装V ...

  5. java从入门到精通_想要开始学java?你要的java从入门到精通布列如下!

    java从入门到精通,让我来告诉你! 毫无疑问,java是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握java看似是一件十分困难的事.其实,只要掌握了科学的学习方法 ...

  6. 虚幻引擎5(UE5)实时VFX游戏特效制作入门到精通

    UE5 Niagara学习教程  课程获取:虚幻引擎5(UE5)实时VFX游戏特效制作入门到精通-云桥网 你会学到什么 我将通过创建各种各样的实时效果来教你虚幻引擎中强大的粒子系统. 我们将从简单的基 ...

  7. Revit:从入门到精通学习教程

    流派:电子学习| MP4 |视频:h264,1280×720 |音频:AAC,48.0 KHz 语言:英语+中英文字幕(根据原英文字幕机译更准确) |大小:8.07 GB |时长:12h 16m Re ...

  8. 《Java 开发从入门到精通》—— 2.2 编写第一段Java程序

    本节书摘来异步社区<Java 开发从入门到精通>一书中的第2章,第2.2节,作者: 扶松柏 , 陈小玉,更多章节内容可以访问云栖社区"异步社区"公众号查看. 2.2 编 ...

  9. meteor从入门到精通_我已经大规模运行Meteor一年了。 这就是我所学到的。

    meteor从入门到精通 by Elie Steinbock 埃莉·斯坦博克(Elie Steinbock) 我已经大规模运行Meteor一年了. 这就是我所学到的. (I've been runni ...

最新文章

  1. 【汇总】细数VSCode中那些能够真正意义提升开发效率、鲜为人知的快捷键
  2. 数据结构与算法 | 线性表 —— 链表
  3. Rsyslog分类过略消息关键字收集程序日志
  4. 使用Gardener在Google Cloud Platform上创建Kubernetes集群
  5. c语言 指针 pdf,深入理解c指针 PDF扫描版[33MB]
  6. laravel构造器的CURD
  7. javaweb框架--自定义标签与freemaker结合
  8. GGally与pairs相关关系图_史上最全(二)
  9. java开发一个银行系统_java实现银行管理系统
  10. ant组件中select默认选中某一项
  11. 谷歌chromeos_如何安装Chrome OS系统
  12. 微信小程序tap事件中target与currentTarget的区别
  13. 计算机学情分析案例,小学信息技术课学情分析
  14. 《疯狂Android讲义》第二版目录
  15. Centos7搭建lamp环境后外网浏览器不能访问
  16. Android Studio 连接雷电模拟器
  17. 实时时钟(Real_Time Clock)电路--中微爱芯
  18. GIT 知识收集-GIT banch 没有显示
  19. 《王者圣域》2.23上线链游玩家|放置塔防、趣味竞技
  20. 50道正则表达式面试题目,你能答对几道?

热门文章

  1. 从前到后的CAN总线(二)
  2. Blueprint简介-Android10.0编译系统(六)
  3. Spark读取MySQL中的数据为DataFrame
  4. 数据库连接查询 变量 运算符
  5. (转)Javascript标准DOM Range操作(1)
  6. MUI - 解决弹出输入法时页面高度变小导致底部上浮的问题
  7. 解决 Windows Docker 安装 Gitlab Volume 权限问题
  8. 解决网页ICON图标无法显示的问题
  9. 网页中英文语言切换解决方案
  10. 解决 “OperationalError: (sqlite3.OperationalError) no such table: ...“问题