对Promises的理解

2022-12-30 0 332

术语签订合同

Promises的基本概念是由CommonJS组核心成员的核心成员在Promises/A规范化中明确提出来的。一般来说,有下列的术语签订合同:

promise(第三个字母大写)第三类指的是Promise示例第三类

Promise第三个字母大写且众数方式,则表示Promise缺省

Promises第三个字母大写且众数方式,用作代指Promises规范化

Promises/A规范化和ES6 Promises规范化

Promise规范化有数次升级换代,目前来说,Promise/A是新一代的民营规范化,ES6 Promises是新一代的非官方规范化,只需晓得ES6 Promises规范化是你假如严格遵守的国际标准要是。

为何有Promises那个小东西

化解反弹圆顶的难题,反弹圆顶也叫反弹绿色植物,动听吧?除了部分名为反弹冥界,不动听了吧?究竟可悲不可悲?十分的可悲。能与此同时管理工作获得成功反弹和失利反弹。

术语解释:并行各项任务和触发器各项任务

并行各项任务:只需JS发动机另一方面就能顺利完成的各项任务,叫并行各项任务。触发器各项任务:JS发动机另一方面难以顺利完成,须要力矩帮助的各项任务,叫触发器各项任务;除了一类是由JS发动机能分立顺利完成,但JS发动机把各项任务分成了几段,第三段当作反弹,也是触发器各项任务。

单纯认知JS发动机的继续执行监督机制的话,触发器各项任务分成四个继续执行段,对JS发动机来说是继续执行第三和第二个期:

触发器各项任务既存继续执行:由JS发动机并行继续执行触发器各项任务力矩继续执行:由力矩继续执行触发器各项任务反弹继续执行:由JS发动机触发器继续执行

简而言之“允诺”

Promise那个单字的原意是“允诺”,是他们日常生活说的“我的确帮你买午饭”、“你假如交了钱我的确给你一碗红酒”。

在程序世界,举例能说:“我允诺给你顺利完成这些代码继续执行”。new一个Promise示例,是JS发动机对你做了一个允诺。

既然是允诺,就的确有获得成功的时候有失利的时候,比如我帮你买早餐,结果今天煎饼果子没出摊,或者是我走到半路上,煎饼果子的塑料袋破裂,煎饼果子滑落到了地上,这是失利。就连“他们允诺绝不首先动用核武器”都有坚持不下去的时候,所以只要是允诺就有获得成功和失利,只不过是概率难题。

到程序世界,一个允诺也会有三种状态,是“未决的”、“获得成功的”、“失利的”三种状态。也是pendding/resolved/rejected三种状态。

Promise缺省的超能力

Promises写法的本质是把触发器写法撸成并行写法。要做这么酷炫这么变态的事情,当然须要Promise缺省有超能力,它的超能力是传入Promise缺省的函数参数会第三优先继续执行,无论那个函数多么的繁复,有多少层反弹,有多少秒的计数器,统统都会最优先继续执行,也是说,他们只要new了一个Promise(),那么Promise缺省的函数参数是最高优先级继续执行,一直到new出一个promise第三类示例,后面的.then()代码才会继续执行。链条上的每一个.then都会等前面的promise有了结果才会继续执行,Promise缺省的那个超能力是Promises系统的威力之源。(当然,这里说的继续执行优先级,是在理想环境下,简而言之理想环境也是全部继续执行代码只由new Promise()和它的一系列.then()方法组成。假如方法链之外除了其他代码,那么整体代码继续执行的先后顺序就复杂化了,下文有介绍。然而,Promise加它的then方法链已经提供了梳理代码继续执行顺序的整套方案,假如在方法链之外还写代码不然属于画蛇添足、自找麻烦,是不科学的写法,假如避免这么做。)

实现了Promise/A规范化的浏览器

单纯说,IE全不支持,Edge支持,Chrome和Firefox在十几个版本之前就已经接近支持,现阶段的新一代版已经全面支持。

所以,IE8-10能考虑Promise/A的Polyfill库:

jakearchibald/es6-promise一个兼容 ES6 Promises 的Polyfill类库。 它基于 RSVP.js

那个兼容 Promises/A+ 的类库, 它只是 RSVP.js 的一个子集,只实现了Promises 规定的 API。

yahoo/ypromise这是一个分立版本的 YUI

的 Promise Polyfill,具有和 ES6 Promises 的兼容性。 本书的示例代码也都是基于那个 ypromise 的 Polyfill 来在线运行的。

getify/native-promise-only

以作为ES6 Promises的polyfill为目的的类库 它严格按照ES6 Promises的规范化设计,没有添加在规范化中没有定义的功能。 假如运行环境有原生的Promise支持不然,则优先使用原生的Promise支持。

其他除了很多Polyfill类库,不多说,能github一下。

基本用法

案例1:现在开始,延迟3秒,继续执行console.log(第三个反弹),然后定义一个变量a,值是3,然后再延迟2秒,继续执行console.log(第二个反弹),然后继续执行console.log(a * 2)。反弹绿色植物型写法是:

setTimeout(function() {

console.log(第三个反弹);

var a = 3;

setTimeout(function() {

console.log(第二个反弹);

console.log(a * 2);

}, 2000);

}, 3000);

继续执行上面代码,结果是:先延迟3秒,然后浏览器打印了一个第三个反弹字符串,然后又延迟2秒,然后浏览器打印了一个第二个反弹字符串,以及打印了一个6数字。

然后遵循Promises的写法是:

var promise = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第三个反弹);

var a = 3;

if ( true ){

resolve(a);

} else {

reject(bu ok);

}

}, 3000);

});

promise.then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

}, 2000);

}, function(error) {

console.log(error);

});

继续执行结果跟绿色植物写法的结果完全一致。

Promise是一个缺省,用来生成promise示例。Promise缺省接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript发动机提供,不用自己部署。

promise第三类代表一个触发器操作,有三种状态:Pending(进行中)、Resolved

(已顺利完成)和Rejected(已失利)。

resolve函数的作用是,将promise第三类的状态从“未顺利完成”变为“获得成功”(即从Pending变为Resolved),在触发器操作获得成功时调用,并将触发器操作的结果,作为参数传递出去。reject函数的作用是,将promise第三类的状态从“未顺利完成”变为“失利”(即从Pending变为Rejected),在触发器操作失利时调用,并将触发器操作报出的错误,作为参数传递出去。

promise第三类生成以后,能用then方法分别指定Resolved状态和Reject状态的反弹函数。

then方法能接受两个反弹函数作为参数。第三个反弹函数是promise第三类的状态变为Resolved时调用,第二个反弹函数是promise第三类的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受promise第三类传出的值作为参数。

然后这里你可能会问,if ( true ){}是什么鬼?因为console.log(第三个第三类);var a = 3;是不可能失利的,这都能失利不然,等于js发动机挂了,也等于页面挂了。所以我只能模拟获得成功和失利状态,现在你把true改成false试试,那么,延迟2秒之后是不是打印了bu ok?

比较两种写法的区别,首先就能看出Promises写法的代码多了很多。假如你确定promise第三类根本不可能有失利的状态,能省掉reject函数以及错误反弹。那么能简写成这样:

var promise = new Promise(function(resolve) {

setTimeout(function() {

console.log(第三个反弹);

resolve(3);

}, 3000);

});

promise.then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

}, 2000);

});

上面代码是只考虑promise第三类“获得成功”的可能性,不考虑失利的可能性。

去掉promise第三类失利的可能性之后,你可能继续会说,“Promises写法的代码还是多!”没错,确实多,但不要只看劣势不看优势,没有优势的小东西,是没人会用的。假设反弹多起来了,比如至少5个,而且每一步反弹都有获得成功和失利状态,那么Promises的优势才能显现出来。

案例2:在案例1的基础上,再延迟1秒,继续执行console.log(第二个反弹); console.log(value * 2);,也是说,我想在第二步的输出值的基础上再乘以2,也是想得到12。代码如下:

var promise = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第三个反弹);

resolve(3);

}, 3000);

});

promise.then(function(value) {

return new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

resolve(value * 2);

}, 2000);

});

}).then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

}, 1000);

});

结果没难题:

对Promises的理解

Paste_Image.png

这里用到了then的链式调用。你会发现第三个then返回了一个promise第三类。这就跟案例1不一样了,案例一的then里没有再返回promise第三类。必须返回么?看案例3。

案例3:跟案例2相似,只是继续执行console.log(第二个反弹)这步不用延迟。代码如下:

var promise = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第三个反弹);

resolve(3);

}, 3000);

});

promise.then(function(value) {

console.log(第二个反弹);

console.log(value * 2);

return value * 2;

}).then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

}, 1000);

});

跟案例2的代码的区别是什么?第三个then方法中,没了return promise第三类,取而代之的是return value * 2,为何?

因为案例2中,四个then的回调函数是触发器-触发器-触发器,案例3中,是触发器-并行-触发器,这区别很大。单纯情况下,then方法中的反弹继续执行代码是并行代码,这样只须要单纯return一下参数,就能把参数传递下去。复杂情况下,是触发器-异步-触发器这种情况,假如依然单纯的在setTimeout的反弹里return一下参数,你会发现,参数根本没有及时传递。代码如下:

var promise = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第三个反弹);

resolve(3);

}, 3000);

});

promise.then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

return value * 2;

}, 2000);

}).then(function(value) {

setTimeout(function() {

console.log(第二个反弹);

console.log(value * 2);

}, 1000);

});

结果就神奇了:

对Promises的理解

Paste_Image.png

什么原因?怎么认知?

单纯说原因是:

then的链式继续执行,理想情况下是基于每个then的前一个then能返回promise第三类。不理想情况下是没有返回promise第三类,这种情况下,虽然then的链式继续执行依然能继续执行,但,每个then只可能等前一个then的并行代码顺利完成,不会等前一个then的触发器代码顺利完成。第三个then的并行代码是一个计时器,开始计时就算顺利完成了,然后第二个then什么也没得到,其实是得到了一个undefined,undefined * 2得到NaN,所以打印NaN。

为啥第三个then的反弹用return promise第三类,第二个then就能等那2秒的延迟呢?用上段文字就很好认知了,而且你还能回忆一下本文最上方说的promise第三类的超能力,当你给then返回一个非promise第三类,then只接收并行的返回值,反之,当你给then返回一个promise第三类,那么then就等待promise第三类生成,然后等resolve和reject传递参数,等多久都能等。

这里要注意一下,我上段文字所说的“等待”,其实并不是等待,而是new一个promise第三类的过程被js发动机视为最高优先级继续执行,因此new出了promise第三类,并return的过程,其实是并行代码,then其实并不是在等待,而是十分自然的链式继续执行顺序。

为什么第二个反弹比第二个反弹先继续执行了?因为第二个then得到了undefined之后,第二个then就开始了,第二个反弹延迟的时间短嘛,就1秒,所以比第二个反弹先继续执行了。

为啥“第二个反弹”下面输出是6?因为3 * 2得6。

总之,假如想触发器-触发器-触发器-触发器……这样一直搞下去,就只能是每一步都给下一步返回一个promise第三类。

解答几个难题:

假如resolve或reject语句后面还写了语句,会继续执行吗?

会。resolve或reject负责传参,但不是说传了参就中止继续执行了。

假如第三个then的反弹用了promise第三类,但promise第三类没写resolve或reject方法,第二个then的反弹还会继续执行么?

答案是不继续执行,等于第二个then白写了,因为promise第三类永远处于Pending状态。假如后面有第二个then,依然不会继续执行。等于链条从第三个promise第三类就断了。

假如有resolve或reject方法,但不设参数,也是resolve()或reject(),那么then会继续执行吗?

会。resolve或reject传的参数是undefined。

假如第三个then的第三个回调函数没继续执行,第二个then的第三个反弹函数会继续执行么?

会。记住,在Promises的链式操作中,没继续执行的代码就认为是返回undefined就对了。

假如promise只继续执行了reject方法,但第三个then没有写对应的error处理反弹,第二个then写了,还能处理么?

能。Promises规定,参数能无限制的顺着链条传递下去直到被处理掉。

假如流程是触发器-并行-并行-并行…..这么走下去,那么搞一串then除了意义吗?所有并行合在一起岂不是更容易编写?也容易认知?

答案:假如真的是一串的并行,当然能合并了。Promises的用武之地在于全触发器或者触发器-并行互相夹杂的情况。

then的反弹的优先级有多高?

测试一下:

console.log(sync1);

setTimeout(function() {console.log(setTimeout1)}, 0);

var promise = new Promise(function(resolve, reject) {

setTimeout(function() {console.log(setTimeoutPromise)}, 0);

console.log(promise);

resolve();

});

promise.then(function() {

setTimeout(function() {console.log(setTimeoutThen-1)}, 0);

console.log(then-1);

}).then(function() {

setTimeout(function() {console.log(setTimeoutThen-2)}, 0);

console.log(then-2);

});

setTimeout(function() {console.log(setTimeout2)}, 0);

console.log(sync2);

会得到

sync1

promise

sync2

then-1

then-2

setTimeout1

setTimeoutPromise

setTimeout2

setTimeoutThen-1

setTimeoutThen-2

也是说,new Promise()的继续执行是并行的,.then方法的继续执行也是并行的,但,new Promise()内的反弹函数的优先级跟外部反弹函数的优先级是平级的,而.then方法的反弹的优先级比外部下方的反弹的优先级要低。也是说,.then方法的反弹里面的并行各项任务,优先级比new Promise()外面的触发器各项任务的优先级高;而.then方法的反弹里面的触发器各项任务,优先级比new Promise()外面的触发器各项任务的优先级低。可见,then的反弹函数的优先级并没有什么特别的。

所以,从p第三类赋值语句开始,JS发动机的继续执行顺序是:

new Promise()内的并行各项任务

-> 外部下方的并行各项任务

-> .then链里的所有反弹里的并行各项任务

-> 外部的触发器各项任务的反弹,加上new Promise()内的反弹各项任务,按反弹时间依次继续执行

-> .then链里的所有反弹里的反弹各项任务

由此能看出,如果在then链条之外还写代码不然,优先级会比较混乱,就像本文开头说的,在then链条外面写代码不是一个好主意,假如由new Promise()和then链条统一管理工作本次须要继续执行的所有代码,否则优先级不容易把控。

Promise.prototype.catch()

.catch()是什么?它是.then()的一个子集,也是说专用作接收promise第三类的reject()传过来的error参数的。其他没什么特别的。也是说,.then()能有两个反弹函数,.catch()只有一个反弹函数。永远尽量在能用.catch()的场合全用.catch()。

.catch()能链式调用么?

能。能跟.then()混合链式么?能。

假如上一层的.then()没有传递error参数,.catch()会继续执行吗?

不会,会被JS发动机跳过。继续问,.catch()下一层假如是.then(),会继续执行吗?会。参数怎么传递?跳过不继续执行的代码,该怎么传递就怎么传递。

连写.catch()和.then(//只写一个反弹)是并列关系么?

绝对不是,是继续执行的前后关系。从来没有什么并列关系。连写.then(//只写一个反弹)和.catch()也不是并列关系。

Promise.all()

Promise.all方法用作将多个promise示例,包装成一个新的promise示例。

Promise.all()方法接受一个数组作为参数,p1、p2、p3都是Promise第三类的示例,假如不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise示例,再进一步处理。(Promise.all()方法的参数能不是数组,但必须具有Iterator接口,且返回的每个核心成员都是Promise示例。)

p的状态由p1、p2、p3决定,分成两种情况。

只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的反弹函数。

注意,假如p1的最后一个then的反弹没有return命令,那么p1的返回值是undefined,即使倒数第二个then的回调有返回值也没有用,只看最后一个then的反弹的返回值。

再注意,组成的数组的顺序是按照.all()参数的书写顺序而定,跟谁先返回值无关。只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第三个被reject的示例的返回值,会传递给p的反弹函数。

下面是一个具体的例子。

var p1 = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第三个反弹);

reject(3);

}, 3000);

});

var p2 = new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第二个反弹);

reject(2);

}, 2000);

});

Promise.all([p1, p2]).catch(function(value) {

return new Promise(function(resolve, reject) {

setTimeout(function() {

console.log(第二个反弹);

resolve(value * 2);

console.log(value * 2);

}, 2000);

});

}).then(function(value) {

setTimeout(function() {

console.log(第四个反弹);

console.log(value * 2);

}, 1000);

});

结果是下图。由于p1和p2都rejected,所以catch捕获的参数是p2传过来的,因为p2的延迟比p1短。

对Promises的理解

Paste_Image.png

假如p1的反弹是并行各项任务,p2是触发器各项任务,毫无疑问,catch捕获的参数会是p1传过来的,但,通常的确不这么用,这么写太蠢了。假如p1和p2都是并行各项任务?更蠢,那么你干嘛不把p1和p2写到一起呢?而且,根本不须要Promises写法。

假如p1和p2都fulfilled,那么value是一个数组。不代码举例了。

总结:Promise.all()方法的适用场合,是多个触发器各项任务并发继续执行,在最后一个各项任务获得成功顺利完成之后,给出一个反弹。比如,并发10个xhr线程,传10个文件,你并不晓得哪个文件会先传完,Promise.all()方法能确保在10个文件都传完的那一刻给出顺利完成提示。

假如不用Promise.all()方法,通常做法是设一个计数器初始值为0,每上传获得成功一个文件就+1,然后判断一次,看看计数器是否等于10,假如等于不然,就给出完成提示。最终相当于判断10次。

Promise.race()

Promise.race方法同样是将多个Promise示例,包装成一个新的Promise示例。

var p = Promise.race([p1,p2,p3]);

上面代码中,只要p1、p2、p3之中有一个示例率先改变状态,p的状态就跟着改变。那个率先改变的Promise示例的返回值,就传递给p的反弹函数。

注意,简而言之率先改变状态,可能会改成fulfilled,也可能会改成rejected,都能。

Promise.race方法的参数与Promise.all方法一样,假如不是Promise示例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise示例,再进一步处理。

Promise.race的使用场合不算常见,比如一款小游戏,三辆赛车比赛,任何一个车先到达终点,比赛就结束,那么能适用作Promise.race。或者是一个躲开障碍的游戏,任何一个障碍物撞到你,游戏就结束,那么能适用作Promise.race。

还一个场合是超时判定。比如一个ajax请求,30秒下载不下来就算失利。这样,p1是ajax请求,p2是30秒计数器,谁先顺利完成,p的状态就随谁。那个率先改变的Promise示例的返回值,就传递给p的反弹函数。

Promise.resolve()

有时须要将现有第三类转为Promise第三类,Promise.resolve方法就起到那个作用。

var jsPromise = Promise.resolve($.ajax(/whatever.json));

上面代码将jQuery生成的deferred第三类,转为一个新的Promise第三类。

Promise.resolve方法的参数分成四种情况。

(1)参数是一个Promise示例

假如参数是Promise示例,那么Promise.resolve将不做任何修改、原封不动地返回那个示例。

(2)参数是一个thenable第三类

thenable第三类指的是具有then方法的第三类,比如下面那个第三类。

var thenable = {

then: function(resolve, reject) {

resolve(42);

}

};

var jsPromise = Promise.resolve(thenable);

jsPromise.then(function(value) {

console.log(value); // 42

});

(3)参数不是具有then方法的第三类,或根本就不是第三类

假如参数是一个原始值,或者是一个不具有then方法的第三类,则Promise.resolve方法返回一个新的Promise第三类,状态为Resolved,示例p向then的反弹函数传的参数是那个原始值。

var p = Promise.resolve(Hello);

p.then(function (s){

console.log(s) // Hello

});

上面代码生成一个新的Promise第三类的示例p。由于字符串Hello不属于触发器操作(判断方法是它不是具有then方法的第三类),返回Promise示例的状态从一生成是Resolved,所以反弹函数会立即继续执行。Promise.resolve()方法的参数,会与此同时传给反弹函数。

(4)不带有任何参数

Promise.resolve()方法允许调用时不带参数,直接返回一个Resolved状态的Promise第三类。示例p向then的反弹函数传的参数是undefined。

所以,假如希望得到一个Promise第三类,比较方便的方法是直接调用Promise.resolve()方法。

var p = Promise.resolve();

p.then(function (s){

console.log(1)

});

console.log(2);

上面代码的变量p是一个Promise第三类。

Promise.reject()

Promise.reject()方法也会返回一个新的promise示例,该示例的状态为rejected

。它的参数用法与Promise.resolve()方法完全一致。

最佳实践

现在Promises规范化全部介绍完了。然后是最佳实践。

参考文档:

promises 很酷,但很多人并没有认知就在用了打开Promise的正确姿势

作者:microkof

链接:https://www.jianshu.com/p/b497eab58ed7

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务