一首歌带你搞懂Promise
发布时间:2023-06-24 05:01:59 作者:互联网收集 浏览量:83
Promise简介
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。(摘自<ECMAScript 6 入门>),但是对于Promise的创建和执行流程,其实有很多人初学时,容易走入误区。
今天就以一首歌来带大家学习Promise。
Promise创建
Promise翻译过来的意思就是'承诺',其实Promise的创建本质就是一个许下承诺的过程,想到这我的脑海中不由得浮现了一首经典老歌,海鸣威 《你的承诺》。
歌中相知相恋的两个人,奈何情深缘浅,终于还是分开,许下了“不为彼此难过, 过各自的生活'这样的承诺。但是随后歌曲又以'Oh baby 你答应我的我都记得 ,但是你却忘了你的承诺 ,不是说好彼此都不再联络, 谁都别再犯错',暗示承诺的状态发生改变。
由此不难想象以下两种情况:
同样,JS中的Promise的创建也是许下代码承诺的一种方式,它默认也包含三种状态:
那么如何许下JS中的承诺呢:
Promise中接收一个回调函数(简单说就是一个容器),传入某个未来才会结束的事件(通常是一个异步操作)的结果;此时打印,你会得到一个Promise实例(实例化对象),展开之后默认显示:
此时我们会看到
但是有人就会问了,既然有三种状态,而默认是pending(进行中)状态,那么我们如何将当前Promise实例的状态改为另外两种:
这就不得不提到Promise的第一特征了:
对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
可以理解为,状态并不是自行改变的,而是由异步(同步也可以)的结果决定了.。
所以Promise在创建过程中,传入的回调函数接收了改变状态的两个方法 ,resolve,reject作为形式参数传入.
resolve() 将Promise实例的状态 由pending(进行中) 改为 fulfilled(已成功)
reject() 将Promise实例的状态 由pending(进行中) 改为rejected(已失败)
而且resolve() reject() 方法在调用时 还可以接收一个参数,作为数据传入,存储到Promise实例中。
同步代码展示:
resolve() 和 reject() 方法,在同步代码中调用,会立即改变Promise实例的状态。
异步代码展示:
resolv e() 和 reject() 方法,在异步代码中调用:由于JS是单线程,会优先在主线程执行同步代码,异步代码会放到任务队列中,所以在页面加载时,,Promise实例为pending(进行中)状态,等待主线程执行完毕,任务队列通知主线程,异步任务可以执行了,该任务才会进入主线程执行。
此时setTimeout中的回调函数被调用,执行了resolve() 或 reject() 方法, Promise实例会随之改变状态,并存储传入的数据。
那么resolve() reject() 方法,可以重复调用么?
这就要讲到Promise的第二大特征:一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型) 注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。
由此可见Promise本质,就是一个状态机,当该Promise对象创建出来之后,其状态就是pending(进行中),然后通过程序来控制到底是执行已完成,还是执行已失败。
因为Promise处理的是异步任务,所以我们还得对Promise做监听,当Promise的状态发生变化时,我们要执行相应的函数(then catch finally)。
所谓Promise动态方法,即将封装的方法存储在Promise的原型对象(prototype)上,供所有通过构造函数创建的实例化对象使用。
Promise.prototype.then()
Promise 实例具有then方法,它的作用是为 Promise 实例添加状态改变时的回调函数,前面说过 Promise实例的状态可以从pending(进行中) 变为 fulfilled(已成功)或rejected(已失败),所以then方法中接收两个回调函数(它们都是可选的)。
p.then(resolveHandler,rejectHandler)
resolveHandler 第一参数,用于指定Promise实例,由pending(进行中) 变为 fulfilled(已成功)时执行的回调函数。
rejectHandler 第二参数,用于指定Promise实例,由pending(进行中) 变为 rejected(已失败)时执行的回调函数。
可以理解为 then()方法中的两个回调函数,提前规定好了状态改变时需要执行的内容,等待以后Promise实例的状态之后再执行对应的回调函数 (本质还是回调函数,要用魔法打败魔法)。
注意:resolveHandler rejectHandler 中可以传入一个形式参数 接收Promise实例中存储数据。
解析:在异步代码中调用: 由于JS是单线程,会优先在主线程执行同步代码,异步代码会放到任务队列中,所以
还有一个有趣的地方:then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
上面的代码使用then方法,依次指定了两个回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。当然这里有一个前提,那就是Promise实例的状态为已成功且代码执行过程中不出错的情况下。万一出错的话,那我们就可以使用then()方法的第二个回调函数或者catch()方法捕获错误。
Promise.prototype.catch()
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
上面代码中,readText()方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
then和catch的链式操作
then()和catch()配合使用,实现的链式操作效果更佳。
上面的代码使用then catch方法进行链式调用,依次指定了三个then()的回调函数以及一个catch()的回调函数。第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数,同样第二个回调函数完成后,会将返回结果作为参数,传入第三个回调函数,以此类推。当然这里同样也有一个前提,那就是Promise实例的状态为已成功且代码执行过程中不出错的情况下。万一出错的话,那catch()方法刚好捕获链式操作过程中出现的错误。
如果then方法中回调函数的返回值也是一个Promise实例;
上面代码中,第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。如果变为resolved,继续链式调用后面then方法中指定的回调函数,如果状态变为rejected,就会被catch方法捕获。
注意:Promise 实例的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。
所谓Promise静态方法,即将封装的方法存储在构造函数Promise上,由构造函数自身调用。
Promise.all
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
相较于then方法的链式操作,Promise.all()更注重并发,即依次按顺序发送多个请求,等待所有的请求均有结果之后,对应请求顺序将数据依次存放到数组中返回。
通过以上代码可以发现: (摘自ECMAScript 6 入门)
Promise.all()方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p的状态由p1、p2、p3决定,分成两种情况。
(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.race
Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
相较于Promise.all方法, Promise.race方法更侧重状态改变的速度。
通过以上代码可以发现: (摘自ECMAScript 6 入门)
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.allSettled
Promise.allSettled()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
相较于Promise.all方法,Promise.allSettled跟注重于不管每一个Promise实例是成功还是失败,都会将结果依次按请求顺序放到数组中。
值得注意的地方时,在将数据放到数组的过程中,传入的Promise实例的状态不同,放置的结果稍有不同(数组中的每个成员是一个对象,对象的格式是固定的)。
成员对象的status属性的值只可能是字符串fulfilled或字符串rejected,用来区分异步操作是成功还是失败。如果是成功(fulfilled),对象会有value属性,如果是失败(rejected),会有reason属性,对应两种状态时前面异步操作的返回值。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise也有一些缺点。首先,无法取消Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。第三,当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖。
基本用法
async函数的返回值也是一个Promise实例,在async函数执行的过程中,一旦遇到await就会先返回pending(进行中)状态的Promise实例,等待异步操作有结果之后,继续执行await之后的语句,依次类推,语句全部执行完毕且无错误的情况下,则返回的Promise实例会变为已成功,否则会变为已失败。
你学会了么?文章底部留言告诉我喲。
收藏