文章转载至:前端进阶攻略
作者:前端进阶者
promise 是什么?
Promise 是异步编程的一种解决方案,比传统的回调函数和事件更合理和强大。
所谓 Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事情(通常是一个异步操作)。从语法上说,Promise 是一个对象,从他可以获取异步操作的消息。
特点:
对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(以失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来。
一旦状态改变,就不会再变,任何时候都是可以得到这个结果的。Promise 对象的状态改变只有两种可能:* 从 pending 变为 fulfilled 和从 pending 变为 rejected。只要这两种情况发生,状态就会凝固,不会再变了。再对 Promise 对象添加回调函数也会立即得到这个结果。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来。
缺点:
首先无法取消 Promise,一旦新建他就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部跑出的错误无法反应到外部。当 pending 的时候,无法知道进展到了哪一步。
基本用法
ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。
下面代码创造了一个 Promise 实例。
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve 函数的作用是,将 Promise 对象的状态从 ” 未完成 ” 变成 ” 成功 ”。(即从 pending 变为 resolved)。在异步操作成功的时候调用,并将异步操作结果作为参数传递出去;
reject 函数的作用是,将 promise 对象的状态从 ” 未完成 ” 变成 ” 失败 ”(即从 pending 变为 rejected)。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise 实例生成后,可以用 then 方法分别指定 resolve 状态和 rejected 状态的回调函数。
then 方法可以接受两个回调函数作为参数,
第一个回调函数是 promise 对象的状态变为 resolved 的时候调用,
第二个回调函数是 promise 对象的状态变为 rejected 时调用。
其中第二个函数是可选的,不一定需要提供。
这两个函数都接受 Promise 对象传出的值作为参数。
上面代码中,timeout 方法返回一个 Promise 实例,表示一段时间后才会发生的结果。
过了指定的时间以后,Promise 实例的状态变为 resolved,就会触发 then 方法绑定的回调函数。
Promise 新建后就会立即执行。
上面代码中,使用 Promise 包装一个图片加载的异步操作,如果加载成功就调用 resolve 方法,否则就调用 rejected 方法。
- Promise.prototype.then()
Promise 实例具有 then 方法,也就是说 then 方法时定义在原型对象上的。
它的作用是为 Promise 实例添加状态改变时的回调函数。
前面说过,then 方法的第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数(可选)。
then 方法返回的是一个新的 Promise 实例(注意,不是原来那个 Promise 实例)因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。
采用链式的 then 可以指定一组按照次序调用的回调函数。这时,前一个回调函数可能返回一个还是 Promise 对象(即有异步操作),这时候一个回调函数就会等该 Promise 对象的状态发生变化,才会被调用。
上面代码中,第一个 then 方法指定的回调函数,返回的是一个 Promise 对象。这时,第二个方法指定的回调函数,就会等待这个新的 Promise 对象状态发生变化。如果变为 resolved,就调用 funcA, 如果状态变为 rejected,就调用 funcB.
- Promise.prototype.catch()
Promise.prototype.catch 方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
上面代码中,getJSON 方法返回一个 Promise 对象,如果该对象状态变为 resolved,则会调用 then 方法指定的回调函数;如果异步操作抛出错误,状态就会变为 rejected,就会调用 catch 方法指定的回调函数。另外,then 方法指定的回调函数,如果运行抛出错误,也会被 catch 方法捕获。
如果 Promise 状态以及变成 resolved,再抛出错误是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。
Promise 对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止,也就是说错误总会被下一个 catch 语句捕获。
- Promise.prototype.finally()
finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。
4.Pomise.all 的使用
Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
具体代码如下:
Promse.all 在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个 ajax 的数据回来以后才正常显示,在此之前只显示 loading 图标。
需要特别注意的是,Promise.all 获得的成功结果的数组里面的数据顺序和 Promise.all 接收到的数组顺序是一致的,即 p1 的结果在前,即便 p1 的结果获取的比 p2 要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用 Promise.all 毫无疑问可以解决这个问题。
5、Promise.race 的使用
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。
顾名思义,Promse.race 就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
原理是挺简单的,但是在实际运用中还没有想到什么的使用场景会使用到。
举例:超时取消
我们来看一下如何使用 Promise.race 来实现超时机制。
当然 XHR 有一个 timeout 属性,使用该属性也可以简单实现超时功能,但是为了能支持多个 XHR 同时超时或者其他功能,我们采用了容易理解的异步方式在 XHR 中通过超时来实现取消正在进行中的操作。
- 让 Promise 等待指定时间
首先我们来看一下如何在 Promise 中实现超时。
所谓超时就是要在经过一定时间后进行某些操作,使用 setTimeout 的话很好理解。
首先我们来串讲一个单纯的在 Promise 中调用 setTimeout 的函数。
delayPromise(ms)
返回一个在经过了参数指定的毫秒数后进行 onFulfilled 操作的 promise 对象,这和直接使用 setTimeout 函数比较起来只是编码上略有不同,如下所示。
在这里 promise 对象 这个概念非常重要,请切记。
- Promise.race 中的超时
我们可以将刚才的 delayPromise 和其它 promise 对象一起放到 Promise.race 中来是实现简单的超时机制。
函数 timeoutPromise(比较对象 promise, ms) 接收两个参数,第一个是需要使用超时机制的 promise 对象,第二个参数是超时时间,它返回一个由 Promise.race 创建的相互竞争的 promise 对象。
之后我们就可以使用 timeoutPromise 编写下面这样的具有超时机制的代码了。
虽然在发生超时的时候抛出了异常,但是这样的话我们就不能区分这个异常到底是_普通的错误_还是_超时错误_了。
为了能区分这个 Error 对象的类型,我们再来定义一个 Error 对象的子类 TimeoutError。
扩展知识:定制 Error 对象
Error 对象是 ECMAScript 的内建(build in)对象。
但是由于 stack trace 等原因我们不能完美的创建一个继承自 Error 的类,不过在这里我们的目的只是为了和 Error 有所区别,我们将创建一个 TimeoutError 类来实现我们的目的。
在 ECMAScript6 中可以使用 class 语法来定义类之间的继承关系。
为了让我们的 TimeoutError 能支持类似 error instanceof TimeoutError 的使用方法,我们还需要进行如下工作。
我们定义了 TimeoutError 类和构造函数,这个类继承了 Error 的 prototype。
它的使用方法和普通的 Error 对象一样,使用 throw 语句即可,如下所示。
有了这个 TimeoutError 对象,我们就能很容易区分捕获的到底是因为超时而导致的错误,还是其他原因导致的 Error 对象了。
❤️ 看完三件事
如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:
点个「赞同」,让更多的人也能看到这篇内容(喜欢不点赞同,都是耍流氓 -_-)
点个关注不迷路~ 让我们成为长期关系
关注我的专栏(前端高级架构帮),送你高级前端面试题