半年不迭代,迭代搞半年,说的就是我,这里有点尴尬了,直接进入主题吧
我记得在的时候集成了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组织搞出来的一个指南--,建议大家拜读一下,虽然没有详细讲解规范,但是对于Promise使用场景和特性做了一次详细的介绍,包括我上面说的使用场景等等。
以上讲的更多偏向应用层的知识点,下面就让我们深入它的根本去了解Promise是如何实现的。
Promise本身是一种社区规范--,由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。回过头来发现,最值得佩服的是这种规范的设计思维,通过一种设计思维,将简单的技术化腐朽为神奇。所以,个人意见,程序员的进阶,最重要的不是代码的熟练度,而是思维的进阶。熟练度这个是每个人都可以靠时间堆积出来的,但是更高级的工程师,应该能从整体的视角去了解,然后规划和设计,将简单的技术化神奇,将复杂的问题化简单。
整个代码我就不贴出来了,太长了,可以到 上查看,功能完善了,支持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地址: 对你有帮助或启发,点个小星星,支持继续研究下去