共计 3455 个字符,预计需要花费 9 分钟才能阅读完成。
Promise模式的工作原理
为什么需要Promise模式
Promise解释之前,我们提一下华罗庚讲《统筹方法》时提到的烧水喝茶的例子,在烧水的同时做一些其他的泡茶准备工作。这种思想可以让我们合理安排一项任务的工序,从而节约时间。具体如下:
我们在这里简化一下,借用它来讲解Promise模式。
假设为了泡茶,我们需要做两项任务, 分别是:烧水(用时15分钟) 和 准备茶杯, 茶叶(用时3分钟)。
泡茶任务的两种执行方式
同步执行
即在开启烧水任务后,等待水沸腾, 发出蜂鸣声后(获取到烧水任务完成的信号),再开启另一项任务,开始准备茶杯和茶叶(用时3分钟),则整个泡茶过程需要18分钟。如图:
异步执行
在开启烧水任务后, 并不需要等待烧水任务全部完成,,就可以执行准备茶杯茶叶的任务,完成准备茶杯茶叶任务后,,等待烧水任务执行结束的信号(蜂鸣声),则完成了泡茶的准备工作,整个过程需要15分钟,比同步执行更快。如图:
以上便是一个Promise模式在日常生活中的一个运用。Promise模式是一种异步编程模式. 其核心的是, 执行任务A(例如烧水)时, 其执行结果可通过一个promise对象来获取。因而并不需要同步等待任务A结束再去执行任务B。任务B可以与任务A同时执行,它需要任务A执行的结果时, 通过这个promise对象获取即可(例如去听蜂鸣声)。
在烧水喝茶的例子,Promise模式的好处显而易见,泡茶的总时长减少了。在程序中,这意味着程序会运行更快,因为Promise可以避免不必要的等待, 提高对并发的支持。
Promise模式介绍
Promise模式主要由4部分构成:
- Promisor
- Executor
- Promise
- Result
下面一幅图说明这个四个部分的构成关系,:
- Executor需要在Promisor的create()方法中去执行。
- create()的返回值就是Promise。
- Result则是Promise中get()方法的返回值。
看到这里, 如果你还是觉得云里雾里的,没关系,下面我们结合例子详细讲解。如图:
Promisor类: 对外暴露一个方法create(), 一旦执行了这个create()方法, 两件事情就发生了:
1、执行器Executor开始异步执行任务。
2、Promisor会创建并返回一个Promise。
这就好比, 一方面开启了烧水的这个异步任务, 另一个方面开始等待水沸腾的蜂鸣声音。如图:
Executor: 在Promisor中定义好一个任务, Executor异步执行这个任务。换句话来讲, 就是Promisor决定了,接下来要去异步开启烧水, 然后按下Executor的按钮,它就开启行动啦。
Promise: 是Promisor的create()方法的返回值,这里是立刻返回的,就像买彩票时候拿到的号码。虽然在买彩票的那一刻,不知道结果, 不过你也不需要在彩票站等待, 直接拿着号码就可以回去了。等开奖时间到了, 就可以用这个号码查询自己有没有中奖。 而Promise就好比这个号码,可以立刻返回得到,不会阻塞程序运行.。
方法介绍:
1、isDone()方法:
烧水的结果, 可能是沸腾了,也就是烧好了。也可能正在加热中。所以Promise中一般会有方法isDone()方法表示任务是在执行中还是已经结束了,放到彩票的例子中就像会告诉你彩票开奖时间是否到啦, 但是不会告诉你结果。
2、get()方法:
获取任务执行的结果, 就好比, 烧水是加热到沸腾, 发出峰鸣声音啦, 还是把水壶给烧漏掉了, 失败啦。此时, 聪明的你们, 可能要问, 这不是要等上一段时间才能知道吗? 刚刚开始烧水, 怎么能知道结果是沸腾了, 还是水壶漏了呢? 这就引出了get()方法的关键:需要阻塞等待, 只有经过了阻塞等待, 才能知道结果哟。放到彩票的例子中, get()方法就像是一直在电视机前等着看开奖结果, 等着等着,直到中奖结果公布。如图
Result: 最简单的一个部分, 其实就是Promise中get()方法的返回值, 放到彩票的例子中, 就是开奖结果。
上面我们详细分析了这四个部分的结构关系, 下面再从时序的角度来再次看待一下这4个部分.如图
没有Promise模式时, 泡茶问题的代码如何执行?
下面用java代码来模拟烧水例子的全过程。我们会分别用 非Promise模式 和 Promise模式 来写两套代码进行对比。
非Promise模式泡茶
先设定两个类, 一个是BoilWater表示烧水,在代码中new BoilWater()表示开始烧水,其中status等于true表示烧水完成。另一个是TeaAndCup表示准备茶叶茶杯,同样, 在代码中new TeaAndCup表示开始准备茶叶茶杯,其中status等于true表示准备茶叶茶杯完成。如图:
做好了铺垫,下面就可以看非Promise模式时的代码啦,如图:
运行结果如下: 如图
可以看到,任务一(烧水) 和 任务二(准备茶叶茶杯) 因为一个join()方法的介入,先把任务一(烧水)结束之后,再开始执行任务二。这时两个任务是串行执行的,多线程也失去了意义。
看到这里,好奇的你可能会问,为啥需要执行join()方法呢? 异步执行不就可以了吗, 为了回答这个问题, 我们把join()注释掉, 再来执行以下程序, 看一看结果。没有join()的程序的执行结果,如图
可以看到,任务一结束竟然是在准备工作结束之后。这说明任务一(烧水)还没执行结束。主程序就结束了。 烧水还没结束,主程序就任务准备工作结束啦,这显然是不符合要求的。如图
这真是矛盾啊,如果串行执行呢,结果是正确的,可是没有做到异步。效率不高;如果直接开启异步任务呢, 主程序又提前执行结束了, 如何解决这个问题呢? 那就需要Promise模式了! 这个代码,下回分解!
总结与展望
本篇文章先让大家讲了一个经典的烧水泡茶的例子,说明需要异步多线程执行,效率才更高,。也就是使用Promise模式的原因了。 再结合流程图和时序图, 讲解了Promise模式的原理, 主要是解释了1:Promisor, 2:Executor, 3:Promise,4:Result的作用,我们还特别强调了promise中get()方法是阻塞的。 最后, 把泡茶的例子用java代码实现了一下, 通过观察程序的结果,我们再次验证:Promise模式使得我们的程序兼顾了高效率与正确性。
下篇文章我们会在Promise模式下使用java代码实现泡茶的例子,并和这篇文章结尾中非promise模式的代码做个对比。彻底理解了Promise模式之后,我们会深入一个更复杂的例子当中,去探讨一下,如何解决个人云盘本地文件同步太慢的问题。敬请期待!