ES6——Promise
Promise是ES6中新增的一个对象,主要是为了解决异步操作的问题而新增的一个方法
定义
Promise用来表示一个异步的状态(成功/失败)以及返回的值
也就是说,Promise里放着的是未来要做的事情
关于执行的顺序,Promise对象在创建后会立即执行,then在当前所有同步脚本执行完后执行
例如如下案例
输出的顺序为:Promise、Hello、resolved
解析如下:
1.Promise对象创建后立即执行,因此输出了Promise
2.then对象中的语句要到当前脚本所有同步任务执行完后再执行,所以resolved暂时不输出,先输出Hello
3.最后,当前脚本内所有的同步任务执行完,执行then里的语句,resolved输出
下面一个案例也是关于异步同步问题的
输出结果为:123465
过程如下:
1.1、4、6不用过多说明,同步任务,按照代码的先后依次执行即可
2.Promise实例在创建的时候就立刻执行,所以2和3也依次输出
3.由于then方法是在所有同步任务执行完后执行,所以5最后输出
状态
Promise里共有三种状态:进行中(pending)、成功(fulfilled)、失败(rejected)
其中状态的改变称为决议,而状态只可以从pendding变更到fulfilled/rejected,不可逆也不可在fulfilled/rejected里互换
也就是说在Promise里,只有pending--->fulfilled或者pending--->rejected,没有第三种
基本方法
使用时,需要先构建一个Promise对象的实例
这个实例接收两个参数,一个是回调成功时执行的函数(习惯性命名resolve),另一个是回调失败时执行的函数(习惯性命名reject)
而如果想在某次回调后继续链式回调,则返回构建Promise对象实例的函数,这样一来,就是返回了一个Promise实例,继续等待传入回调成功和失败时的执行函数
当然,还可以向resolve和reject里传入参数,回调的时候可以输出,但只可以传入一个参数,多余的参数不被接收
使用的时候,利用Promise实例中的then方法,接收两个参数,第一个为回调成功时执行的函数(resolve),第二个是回调失败时执行的函数(reject)
而如果想链式调用,则直接在执行完resolve/reject后返回Promise实例,这样一来,下次继续等待传入两个回调函数
实际上也可以不返回Promise实例,这样一来then里的函数会同之前所有Promise实例后的then一同执行
例如这里的1和2都是1s后输出的,而345则是12输出后的1s后输出(因为返回的Promise实例是要1s后回调的),可以理解成输出1后返回了一个“啥也没干”的Promise
then里如果想返回Promise实例,return一定要写在then的执行函数后面
错误处理
catch
Promise里可以传入reject函数来对错误进行处理,如果没有,需要用catch语句来接收错误并处理
该段代码的执行顺序如下:
1.因为给test输入了true,所以执行的是resolve函数,这里resolve函数传入的参数是ok,而then里的data只是个形参,执行的最后返回Promise对象并传入false
2.第二个链式调用的then里,只传入了resolve函数,没有reject函数,但由于这里Promise对象里是false,应该执行reject函数,所以没有输出
3.第三个链式调用为catch,捕获了第二个then里没有捕获的错误,里面传入的参数可以理解为reject函数,e为形参,最后输出404
但是,如果错误被reject处理了,那后面的catch就不起作用
实际上,可以在最后的catch里再次返回一个错误,让后面的catch解决,但会出现一个问题,有可能最后一个catch抛出了错误,但是没有解决。这一点上,ES6暂时没有解决方法
finally
Promise实例中还有个finally方法,可以放在链式调用的最后,其中的代码无论如何都会执行
注意如果finally执行时还有错误未处理,则会报错,说明Promise里的错误未处理
优点
Promise提高了代码的可读性和可维护性
Promise的使用最大的好处是,解决了之前多层嵌套回调的问题
例如,我们需要创建一个回调函数,用来在1s后执行我们传入的一个函数,如果说只是回调一次,直接调用这个回调函数,传入1s后执行的函数,即可
但如果是要依赖上一次回调的结果,再去执行一次回调函数,那就需要嵌套两层了,不过也还行
可如果是嵌套到五六层呢?嵌套了这么多层以后,如果需要更改其中两次的执行顺序呢?似乎就没办法操作了,堪称回调地狱(如下图)
可如果使用Promise对象来写这一段代码,就变成了下面的样子
相比于之前的层层套的写法,这里采用了链式调用的方法,如果哪里需要修改内容或顺序,可以直接修改then里的内容,或者调整then的顺序,解决了多层嵌套的问题
Promise对象的方法
all
用来接收一个数组,里面每一项是一个Promise实例,最后包装成一个新的Promise实例
注意这里传入的必须是数组(可迭代),否则会报错(not iterable)
分三种情况:
1.所有的Promise决议为成功,.all的决议也为成功,把所有Promise实例的resolve中传入的参数组成一个数组返回,其顺序符合Promise实例的顺序
2.有一个Promise决议为失败,.all决议为失败,把这个决议失败的reject传入的参数作为错误返回
有多个Promise决议为失败时,输出的参数只有第一个reject传入的参数
3.传入空数组,决议为成功,输出then中给resolve传入的参数
race
类似于all,race里传入的参数也是Promise实例,不过返回时,按照第一个返回的决议决定race的决议情况,执行then里的resolve或reject
如果返回的速度一样,则按照传入时的第一个Promise实例的决议来决定执行resolve/reject
但如果传入的为空,则什么都不执行,会一直“挂”在那里
resolve&reject
这里的resolve和reject不是Promise实例或者then里传入的参数,而是Promise实例的方法,用来生成被决议为成功/失败的Promise实例
也就是说,无论传入的是什么,resolve/reject返回的都是一个Promise实例
resolve
resolve方法中可以传入:
1.普通值(字符串、数字等)
下图中的两种写法实际上是一样的
也就是说,then方法最后返回时,可以直接用Promise.resolve()/reject()返回一个决议成功/失败的Promise实例
另外,Promise的all方法里,数组中的每一项都会被resolve方法包裹一下,这样每一项都是一个Promise实例
2.Promise实例
这里传入的Promise实例最后通过resolve方法输出的仍为该实例(最底下一行有验证)
3.thenable对象
thenable对象是一个类似于then方法的对象,传入resolve后,会立即执行其中的then方法,按照then方法的套路走
这里的执行结果为“我被执行了”和“哼”
过程如下:
1.obj作为一个thenable对象被传入的resolve方法,然后后面的then方法执行时,调用obj里的then方法
2.这里的回调函数cb就是console.log(data),也就是输出哼,但在这之前先输出“我被执行了”
reject
而Promise.reject()相比于resolve简单得多,会把传入其中的值直接当成错误信息输出
应用
可以用then变成异步任务并在当前脚本所有同步任务执行后再执行的特性,把同步任务变成异步任务
上方的执行结果为:我是同步任务、我变成了异步任务、2
具体过程如下:
1.用createAsyncTask函数,传入一个匿名函数,这个函数由于传入了Promise的resolve方法,变成了一个Promise实例,但是执行是在后面的then里,因此变成了异步任务,稍后执行
2.“我是同步任务”直接输出
3.输出所有同步语句后,对then里的语句进行执行,由于这里两句都是同步任务,所以按顺序输出
综合案例
可以利用Promise的各种方法实现当图片全部加载完后再载入
(案例待补充)