Promise 作为 ES6 最重要的特性之一。用于一个异步操作的最终完成(或失败)及其结果值的表示:即处理异步请求。我们经常会做些承诺,如果我赢了你就嫁给我,如果输了我就嫁给你之类的诺言。这就是 Promise 的中文含义:诺言,一个成功,一个失败!
1.Promise 构造函数
一般来说 Promise 对象会作为函数的返回值进行调用,它具有两个参数 resolve 与 reject。我们可以这样封装
Promise 是一个构造函数,具有3种状态分别为pending、resolved、rejected。它接受一个回调函数作为参数,并传入两个参数 resolve 与 reject。分别表示异步操作执行成功后的回调、与异步操作失败的回调。reslove 将 Promise 的状态设置为 resolved,reject 是将 Promise 的状态设置为 rejected (这2个参数其实是 Promise 本身的闭包函数)。
2. then() 与 catch()
包装好函数后,会 return 出 Promise 对象。我们就能把原来的回调写法分离出来、在异步操作执行后按不同的状态进行链式的调用执行回调函数。
我们来尝试一下,取一个随机数:
进行调用时,catch 方法与 then 方法几乎相同,为 rejected 的回调。而且 catch 具有捕捉异常并处理的机制,这一点将非常好用,类似 try/catch。
比如我们在执行 getNumber() 时同时打印一个不存在的变量:(虽然 then 中可以传第二个回调函数参数作为 reject 的回调,但通常推荐放在 catch 的回调中)
then 和 catch 这对基友不仅能互相配合,then 甚至可以去返回另一个 Promise,并且共享一个 catch 的回调,形成一套异步任务队列,从而优雅地处理回调:
封装好异步任务后,进行链式调用:
3.all() 与 race() 并行执行异步任务
Promise 为并行执行提供2个函数 all() 与 race(),它们接受一个 Promise 为元素的数组作为参数。它们虽然都是用于并行执行,但它们的 resolve 回调中的 result 则有区别,all 中你可以处理所有的返回数据,而 race 中你只能处理最先返回的返回数据。
有一个场景是很适合使用 all 的,比如游戏类素材较多的应用,初次打开时预加载需要用到各种资源:如图片、flash 以及其他静态文件,所有的资源都加载完后,我们再进行页面的初始化。
all 方法的效果实际上是“谁跑得慢,以谁为准执行”,那么 race 则是“谁跑得快,就执行谁”,race 用法几乎等同于 all
4.串行执行队列事件
Promise 中其实已经有串行执行的方法,也就是 then() 链的不断传递。但是这种方法有时可能不是你最想要的,比如我们无法手动封装每一个事件,或是事件的数量不确定。我们可以将这些返回 Promise 的 function 放入同一个数组,使用 Array.reduce 这个函数,但这是个非常糟糕的做法!
比如我们需要 Async 函数接受某些动态的参数时,如 index,我们为了参数的传递会需要用到闭包。并且这个 reduce 完全没有任何异常的捕获能力,队列只是按照我们预先假象的 then 链执行,就连最后的 catch 都是徒劳,因为只有最后我们才使用了 Promise.resolve() 去连接下方的 catch。
或者是这样:
类似的递归操作或者是队列操作,不仅很难读懂,而且存在很大的隐患!
如果需要,确实可以二次封装请求方法,在请求失败的时候调用 reject,但那样不仅会用到闭包,也需要给所有的事件绑 catch,它们不能再使用同一个 catch 处理 rejected 了。当然,代码实现肯定不是上图那么简单(根据业务需求各不相同,这里就不贴图了)。
既然如此麻烦,那么就用一种方便快捷的解决办法吧!这时候,就需要 es6 的新语法:aysnc、await
是不是简洁了很多呢?同时提升了可读性,不过这颗语法糖底层其实也是闭包与 Promise 配合实现的 ,关于 async、await 这里小A就不在本篇文章中赘述了,今后如有遇到特别好的例子也会配合案例分享。
5.结尾
异步操作在前端可谓是非常重要的一环,上文介绍的 Promise 都是比较基础的使用 demo ,在封装 web API 的请求时,往往还需要更多的考虑。在特别复杂的异步业务逻辑时,如果你接触过或熟悉观察者模式,选择RxJS是一个非常不错的决定,当然它本身也是特别庞大的。
本文代码片段来源自小A的 Github 中Promise-ES6项目。
https://github.com/August-Z/Promise-ES6
领取专属 10元无门槛券
私享最新 技术干货