资讯专栏INFORMATION COLUMN

从状态机的角度加深理解并实现Promise

IamDLY / 937人阅读

摘要:这篇文章内容主要来自一篇高票答案声明此的实现仅仅是为了加深本人对其的理解,和规范有些出入,但是的确是目前看过所有代码中最漂亮,思路比较清晰的一个。

这篇文章内容主要来自一篇stack Overflow高票答案

声明:此Promise的实现仅仅是为了加深本人对其的理解,和A+规范有些出入,但是的确是目前看过所有promise代码中最漂亮,思路比较清晰的一个。
文章不会特意帮助读者复习Promise基本操作。

状态机

Promise其实本质上就是一个状态机,所以首先我们描述一个静态的状态机,就像下边这样

</>复制代码

  1. var PENDING = 0;
  2. var FULFILLED = 1;
  3. var REJECTED = 2;
  4. function Promise() {
  5. // 存储的状态是上边的三个:执行中,已完成,已拒绝
  6. var state = PENDING;
  7. // 存储异步结果或者异步错误消息
  8. var value = null;
  9. // 负责处理中途加入的处理函数
  10. var handlers = [];
  11. }
状态改变

完成了基本的状态机定义,接下来的问题就是完成“状态改变”这个动作的实现:

</>复制代码

  1. var PENDING = 0;
  2. var FULFILLED = 1;
  3. var REJECTED = 2;
  4. function Promise() {
  5. // 存储三个状态
  6. var state = PENDING;
  7. // 一旦出现状态的改变,异步结果就会被存到这个地方
  8. var value = null;
  9. // 存储成功或者失败的handler
  10. var handlers = [];
  11. //状态转移到成功
  12. function fulfill(result) {
  13. state = FULFILLED;
  14. value = result;
  15. }
  16. //状态转移到失败
  17. function reject(error) {
  18. state = REJECTED;
  19. value = error;
  20. }
  21. }

到目前为止,我们给出了两个很纯粹的变化动作,在开发的过程中这两个动作会很不好用,所以我们在这两个动作的基础上构建一个高层次的动作(其实就是加点判断然后封装一层),就像下边这儿,名字就叫做resolve,但是注意和我们正常使用promise调用的那个resolve并不一样,不要搞混:

</>复制代码

  1. var PENDING = 0;
  2. var FULFILLED = 1;
  3. var REJECTED = 2;
  4. function Promise() {
  5. var state = PENDING;
  6. var value = null;
  7. var handlers = [];
  8. function fulfill(result) {
  9. state = FULFILLED;
  10. value = result;
  11. }
  12. function reject(error) {
  13. state = REJECTED;
  14. value = error;
  15. }
  16. //这里暂时缺少两个重要函数getThen和doResolve这两个函数,稍后会说道
  17. function resolve(result) {
  18. try {
  19. var then = getThen(result);
  20. //判断then是不是一个Promise对象
  21. if (then) {
  22. doResolve(then.bind(result), resolve, reject)
  23. return
  24. }
  25. fulfill(result);
  26. } catch (e) {
  27. reject(e);
  28. }
  29. }
  30. }

是的,我们的用到了两个辅助函数getThen和doResolve,现在给出实现:

</>复制代码

  1. /**
  2. * 这里会判断value的类型,我们只要promise.then这个函数,其他的统统返回null
  3. *
  4. * @param {Promise|Any} value
  5. * @return {Function|Null}
  6. */
  7. function getThen(value) {
  8. var t = typeof value;
  9. if (value && (t === "object" || t === "function")) {
  10. var then = value.then;
  11. if (typeof then === "function") {
  12. return then;
  13. }
  14. }
  15. return null;
  16. }
  17. /**
  18. * 这个函数的主要作用就是串主逻辑,完成“变化状态”这个动作
  19. *
  20. * @param {Function} fn A resolver function that may not be trusted
  21. * @param {Function} onFulfilled
  22. * @param {Function} onRejected
  23. */
  24. function doResolve(fn, onFulfilled, onRejected) {
  25. //done的作用是让onFulfilled或者onRejected仅仅被调用一次,状态机状态一旦改变没法回头
  26. var done = false;
  27. try {
  28. //在我们正常使用Promise的时候调的resolve,其实用的就是这里fn注入函数然后调用
  29. fn(function (value) {
  30. if (done) return
  31. done = true
  32. **onFulfilled(value)**
  33. }, function (reason) {
  34. if (done) return
  35. done = true
  36. onRejected(reason)
  37. })
  38. } catch (ex) {
  39. if (done) return
  40. done = true
  41. onRejected(ex)
  42. }
  43. }
构建

好了,一个完整的状态机已经完成,我们完成了一个基本的状态变化逻辑,接下来要做的就是一步一步的朝promise标准进发,这个promise缺少什么呢,暂时缺的就是初始的动作啦(new promise(func)对象一旦被初始化内部代码立即执行),所以我们加上初始动作的开启

</>复制代码

  1. var PENDING = 0;
  2. var FULFILLED = 1;
  3. var REJECTED = 2;
  4. function Promise(fn) {
  5. var state = PENDING;
  6. var value = null;
  7. var handlers = [];
  8. function fulfill(result) {
  9. state = FULFILLED;
  10. value = result;
  11. }
  12. function reject(error) {
  13. state = REJECTED;
  14. value = error;
  15. }
  16. function resolve(result) {
  17. try {
  18. var then = getThen(result);
  19. if (then) {
  20. doResolve(then.bind(result), resolve, reject)
  21. return
  22. }
  23. fulfill(result);
  24. } catch (e) {
  25. reject(e);
  26. }
  27. }
  28. //开启任务的执行,所以我说doResolve其实才是“主线任务”的引子,而fn其实就是你写的代码
  29. doResolve(fn, resolve, reject);
  30. }
联动

我们实现了状态机,但是目前的问题是我们只能眼睁睁的看着代码的流动直到一个Promise结束为止,即没法添加也没法获取结果,这就有很大的局限性了,所以我们要使用then方法来串联Promise,用done方法来完成结果的收集,首先实现done方法,因为then其实说白了就是收集上边的结果--完成自己的逻辑--把结果传递给下一个Promise,做的事情是done的超集。

</>复制代码

  1. var PENDING = 0;
  2. var FULFILLED = 1;
  3. var REJECTED = 2;
  4. function Promise(fn) {
  5. var state = PENDING;
  6. var value = null;
  7. var handlers = [];
  8. function fulfill(result) {
  9. state = FULFILLED;
  10. value = result;
  11. //专门封装一个handle函数处理后续逻辑,在下面有this.handle(handler)方法
  12. handlers.forEach(handle);
  13. //在状态变成已处理并且之前加入的handler都被处理完毕的情况下再加入handler就会报错并且没有卵用
  14. handlers = null;
  15. }
  16. function reject(error) {
  17. state = REJECTED;
  18. value = error;
  19. handlers.forEach(handle);
  20. handlers = null;
  21. }
  22. function resolve(result) {
  23. try {
  24. var then = getThen(result);
  25. if (then) {
  26. doResolve(then.bind(result), resolve, reject)
  27. return
  28. }
  29. fulfill(result);
  30. } catch (e) {
  31. reject(e);
  32. }
  33. }
  34. function handle(handler) {
  35. if (state === PENDING) {
  36. handlers.push(handler);
  37. } else {
  38. if (state === FULFILLED &&
  39. typeof handler.onFulfilled === "function") {
  40. handler.onFulfilled(value);
  41. }
  42. if (state === REJECTED &&
  43. typeof handler.onRejected === "function") {
  44. handler.onRejected(value);
  45. }
  46. }
  47. }
  48. //注意看下面done方法的实现,里边只有一个异步方法,换句话说就是会立即返回不会产生阻塞,我们之后会在then当中调用done方法,这里的onFulfilled, onRejected就是用户写的处理函数,promise异步的特性就是这样来的。
  49. this.done = function (onFulfilled, onRejected) {
  50. // ensure we are always asynchronous
  51. setTimeout(function () {
  52. handle({
  53. onFulfilled: onFulfilled,
  54. onRejected: onRejected
  55. });
  56. }, 0);
  57. }
  58. doResolve(fn, resolve, reject);
  59. }

最后,我们来实现Promise.then,完成状态机的串联:

</>复制代码

  1. //这段代码有点绕,主要需要完成的工作其实就是,判断上一个Promise是否完成,然后执行用户的then里边的回调代码,并最终返回一个新的Promise,然后依次循环。。。
  2. this.then = function (onFulfilled, onRejected) {
  3. //开启then之后就会返回一个新的promise,但是这个时候我们还可能有上一个Promise的任务没有完成,所以先把上边一个promise对象的this指向保存下来
  4. var self = this;
  5. //返回一个新包装Promise,这和我们普通的在外边写new Promise是一个道理
  6. return new Promise(function (resolve, reject) {
  7. //done的代码同样是立即返回,然后异步执行的
  8. return self.done(function (result) {
  9. if (typeof onFulfilled === "function") {
  10. try {
  11. return resolve(onFulfilled(result));
  12. } catch (ex) {
  13. return reject(ex);
  14. }
  15. } else {
  16. return resolve(result);
  17. }
  18. }, function (error) {
  19. if (typeof onRejected === "function") {
  20. try {
  21. return resolve(onRejected(error));
  22. } catch (ex) {
  23. return reject(ex);
  24. }
  25. } else {
  26. return reject(error);
  27. }
  28. });
  29. });
  30. }

Over
更多参考请看下面:
简单的实现Promsie
高性能实现Promise,以及专门的wiki

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107272.html

相关文章

  • 源码看 Promise 概念与实现

    摘要:从源码看概念与实现是异步编程中的重要概念,它较好地解决了异步任务中回调嵌套的问题。这些概念中有趣的地方在于,标识状态的变量如都是形容词,用于传入数据的接口如与都是动词,而用于传入回调函数的接口如及则在语义上用于修饰动词的副词。 从源码看 Promise 概念与实现 Promise 是 JS 异步编程中的重要概念,它较好地解决了异步任务中回调嵌套的问题。在没有引入新的语言机制的前提下,这...

    kel 评论0 收藏0
  • 深入理解promise对象

    摘要:前言中的异步,刚开始的时候都是用回调函数实现的,所以如果异步嵌套的话,就有出现回调地狱,使得代码难以阅读和难以维护,后来出现了,解决了回调地狱的问题。 前言 js中的异步,刚开始的时候都是用回调函数实现的,所以如果异步嵌套的话,就有出现回调地狱,使得代码难以阅读和难以维护,后来es6出现了promise,解决了回调地狱的问题。现在我们就自己写代码实现一下promise,这样才能深入理解...

    CoderDock 评论0 收藏0
  • spring statemachine的企业可用级开发指南1-说些废话

    摘要:让我们先看下状态机的概念。下面是状态机模型中的个要素,即现态条件动作次态。因为订单和审批公文都有很多的流程,每个流程都会产生状态的变化,而且流程是这种业务的主轴,其他都是围绕这个流程和状态变化来考虑的,所以看起来蛮适合用状态机来做。 1、背景在我打算学习spring statemachine的时候,我几乎看过了所有网上的中文教程,基本上都处于浅尝辄止的阶段,有几篇讲的比较深入的,都只是...

    BakerJ 评论0 收藏0
  • 白洁血战Node.js发编程 01 状态

    摘要:状态机状态机是模型层面的概念,与编程语言无关。状态机具有良好的可实现性和可测试性。在代码里,这是一个,但是我们在状态机模型中要把他理解为事件。 这一篇是这个系列的开篇,没有任何高级内容,就讲讲状态机。 状态机 状态机是模型层面的概念,与编程语言无关。它的目的是为对象行为建模,属于设计范畴。它的基础概念是状态(state)和事件(event)。 对象的内部结构描述为一组状态S1, S2,...

    fjcgreat 评论0 收藏0
  • 简单理解Javascript的各种异步流程控制方法

    摘要:所以仅用于简化理解,快速入门,依然需要阅读有深入研究的文章来加深对各种异步流程控制的方法的掌握。 原文地址:http://zodiacg.net/2015/08/javascript-async-control-flow/ 随着ES6标准逐渐成熟,利用Promise和Generator解决回调地狱问题的话题一直很热门。但是对解决流程控制/回调地狱问题的各种工具认识仍然比较麻烦。最近两天...

    makeFoxPlay 评论0 收藏0

发表评论

0条评论

IamDLY

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<