半年不迭代, 迭代搞半年, 说的就是我, 这里有点尴尬了, 直接进入主题吧
我记得在这篇博客的时候集成了 Promise 的, 不过那个时候就简简单单的写了一点最基础, 在一些特殊的 case 上, 还是有点问题的, 所以才有了这个博客. 在拜读了 w3c 和 PromiseA + 规范之后, 从头到尾详细的了解了 Promise 这个东西, 然后自己亲手写了一个和 es6 文档拥有相同功能的库.
什么是 promise?
promise 是一个对象, 表示单个异步操作的最终结果.
什么时候使用?
任何一门技术都不是一个 "万金油", 只有在它最合适的场地出现, 才是实现它最大价值的地方, so,Promise 一样逃不过这个 "真香" 定律.
1. one-and-done 操作模型
异步 I / O 操作: 从存储 API 读取或写入的方法可以返回承诺.
异步网络操作: 通过网络发送或接收数据的方法可以返回承诺.
长时间运行的计算: 需要一段时间来计算某些东西的方法可以在另一个线程上完成工作, 返回结果的承诺.
用户界面提示: 要求用户回答的方法可以返回承诺.
2. One-Time "Events" 模型
即使在已经履行或被拒绝之后也可以订阅, 当某些事情只发生一次, 并且作者经常想要在它已经发生之后观察它的状态
3. 更多状态的变化
图像, 字体等资源的加载 loaded 属性, 这个属性仅在资源完全加载是才会实现, 否则拒绝
不建议使用 promise 的场景
1. 任何一个可能不止一次发生的事件, 都不是 one-and-done 模型的
2. 大的流数据, 分步处理流数据, 而无需将流的全部内容缓冲到内存中.
这里有个 w3c 组织搞出来的一个指南 --Writing Promise-Using Specifications, 建议大家拜读一下, 虽然没有详细讲解规范, 但是对于 Promise 使用场景和特性做了一次详细的介绍, 包括我上面说的使用场景等等.
以上讲的更多偏向应用层的知识点, 下面就让我们深入它的根本去了解 Promise 是如何实现的.
Promise 本身是一种社区规范 --Promises/A+, 由 Promise A + 组织进行制定, 它们提供了一个大纲和指导性的方案, 只要能实现其所列规范, 都可以视作实现了 Promise A+,so, 后面的各种变种啊, 什么样的功能, 都可以根据自己所需去设计, 但是基础的方案按照 A+ 组织规范实现就可以.
这个大纲比较啰嗦, 枯燥, 无味, so, 我们靠 3 张我画的图去理解一下 Promise
第一张: promise 整体流程图
1. 实例化 Promise 的时候(定义内部状态的初始值等等), 在同步状态下, 首先会执行初始化代码, 比如: new Promise((res,rej)=>{ console.log('这里就是初始化代码') }), 然后再执行 then 方法. 这个执行顺序在 promise 下是错误的, 因为在实例代码中会首先改变 Promise 状态, 但是前置的 callback 还没有在 then 方法中注入, 所以要做推迟实例代码(setTimeout, 可以将任务推到执行周期之后, 宏任务), 让 then 先跑起来, 注入状态变更需要的前置依赖.
2. 在 Promise 规范中定义了, then 方法, 必须返回一个 Promise,so,Promise2 就是 then 的返回值. 然后 then 的动作就是将所有需要前置依赖的回调函数, Promise 状态, 状态变化的 value 全都存储起来.
3. 等待推迟的实例代码 (官方叫: 异步) 执行之后, 触发了 Promise 状态的变化这个动作, 然后去改变内部定义的状态, 以及状态变化所要执行的操作.
4. 内部状态已经变化完成, 但是 return 的 Promise2 状态还是 pending, 所以我们需要将自身的 Promise 和 Promise2 的状态进行同步以及是否可 then 的持续操作.
以上为 Promise 的整体流程思路, 它就是这样跑起来的. 不过知道这个流程以后, 还是一知半解的, 下面我们就对核心方法 then 进行详细剖析.
第二张: then 方法核心解析
then 方法需要分 3 个状态去解析
1. pending 状态
a. 首先实例化执行 then 方法, 这个时候初始化的内部状态都是 pending, 这个时候, 我们要做一个订阅和发布的设计, 将 then 传入的 resolve 和 reject 的回调进行包装和存储, 并订阅触发动作.(这边的包装是因为订阅的时候, 不仅仅只是执行回调函数, 还需要处理 promise2 的状态同步问题)
b. 在等待出发的状态的时候, 这个时候状态没有变更, 所以还是 keep pending 状态, 而 promise2 也是 pending; 当 promise 触发了 resolve, 这个时候就需要处理之前订阅的回调了, 先改变 Promise 自身的状态, 然后调用 callback, 将 callback 的值传入解析函数, 同步改变 Promise2 的状态; 触发 reject 动作和 resolve 一样
c. 这样, 在整个 pending 链路上, 自身状态和 promise2 状态全都同步改变完成
2. resolve 状态
a. Promise 内部状态以及变更完毕, 内部会存储 PromiseValue 的值, 直接获取 PromiseValue 的值作为参数, 调起 then 方法传进来的 resolveCallback 的函数.
b. 使用同步解析函数, 去同步改变 Promise2 的状态, 以及后续可 then 的操作
3. reject 状态
该状态操作, 同 resolve 操作, 只是变更状态不一样
以上为 then 方法的所有操作流程, pending 的时候最特殊, 有个订阅发布设计来改变自身状态, 然后同步改变 Promise2 的状态. 其他 resolve 和 reject, 都是状态已经变更完毕, 直接取状态变更的值, 处理回调, 然后同步改变 Promise2 的状态值. 这边同步变更 Promise2 的规则, 在 A + 的规范里是有定义的.
第三张: 同步改变 Promise2 状态以及是否可继续 then 的操作
状态同步改变和是否可持续 then 的操作解析流程, 都按照 A + 规范去判断
1. x 代表 callback 的返回值, 首先判断 x 和 Promise2 是否相等, 相等抛出 TypeError 的错误(毕竟如果返回值 x 和 Promise2 是一个对象的话, 那操作就没啥意义了)
2. 判断 x 是否是 Promise 对象, 如果是的话, 说明 x 是可支持 then 的, 然后根据 x 的状态进行操作和同步改变 Promise2 的值, pending 就等待执行结束, resolve 和 reject 就分别改变 Promise2 的状态
3. 如果 x 不是 Promise 对象, 判断 x 是否是对象或者 function, 否的话直接 resolve Promise2 状态. 如果是的话, try-catch 捕获, 定义 then = x.then 是否报错, 如果报错则 reject 掉 Promise2 状态.
4. 判断 then 是否是 function, 如果是则执行 then 操作, 如果 then 方法执行了 reject, 则 reject 掉 Promise2.
5. 如果 then 执行 resolve, 则将 y 替换掉 x, 重新和 Promise2 进行状态的同步改变.
PS: 这里的 x.then 就是下面的这种状况, A + 规范定义这样的返回值代表还是可 then 的, 需要处理
- temp.then(function (x) {
- return {
- then: function(resolve, reject) {
- resolve(42);
- }
- }
- })
至此整个 Promise 就结束了, 从 Promise 怎么去运行, 到核心代码 then 的处理, 以及 Promise2 的同步改变, 这就是所谓的 Promise. 回过头来发现, 最值得佩服的是这种规范的设计思维, 通过一种设计思维, 将简单的技术化腐朽为神奇. 所以, 个人意见, 程序员的进阶, 最重要的不是代码的熟练度, 而是思维的进阶. 熟练度这个是每个人都可以靠时间堆积出来的, 但是更高级的工程师, 应该能从整体的视角去了解, 然后规划和设计, 将简单的技术化神奇, 将复杂的问题化简单.
整个代码我就不贴出来了, 太长了, 可以到 GitHub 上查看, 功能完善了, 支持 all,race,resolve,reject 方法
在 Ajax-JS 的库的变动, 如下:
1. 替换之前有问题的 createPromise 的代码
2. 将 get,post,postForm,obtainBlob,upload 进行改造, 改方法返回都是 Promise(考虑这些都是 one-and-done 模型, 而轮询和大文件切割上传 2 个方法是持续性操作, 所以不做改变)
3. 删除 postJSON,promiseAjax 方法
Ajax-JS 库增加新功能: mock 功能
全局配置参数:
- // mock 功能
- mock: {
- isOpen: true,
- mockData: {}
- },
流程如下:
demo:
- // 全局配置
- Ajax.config({
- baseURL:'http://localhost:3000/',
- mock: {
- isOpen:true,
- mockData: {
- 'post':'我是 mock 数据'
- }
- }
- })
- // 测试代码
- function request_post() {
- Ajax.post('post',{data:'ajaxPost'})
- .then(x=>{
- console.warn(x)
- })
- }
测试结果:
注意: mockData 的 key 是 url 的值, 不是 baseUrl+url 的值
结束语:
Ajax-JS.1.9.2 完成了, 一直在思考还有什么需要改进的东西, 之后的迭代需要走的方向
1. 完成 http 其他协议, put,delete 等等
2. NPM 的包面向现代化, 去除各种 polyfill 和一些兼容代码
3. 配置 webpack 自动打包压缩
4. 探索通信和其他技术的结合玩法
5. 等等...
GitHub 地址: https://github.com/GerryIsWarrior/ajax 对你有帮助或启发, 点个小星星, 支持继续研究下去
来源: https://www.cnblogs.com/GerryOfZhong/p/10726306.html