ES6Promi⽤法讲解
所谓Promi,简单说就是⼀个容器,⾥⾯保存着某个未来才会结束的事件(通常是⼀个异步操作)的结果。
ES6规定,Promi对象是⼀个构造函数,⽤来⽣成Promi实例。
下⾯代码创造了⼀个Promi实例。
constpromi=newPromi(function(resolve,reject){
//...somecode
if(/*异步操作成功*/){
resolve(value);
}el{
reject(error);
}
});
Promi构造函数接受⼀个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不⽤⾃⼰
部署。
resolve函数的作⽤是,将Promi对象的状态从“未完成”变为“成功”(即从pending变为resolved),在异步操作成功时调⽤,并将异步操
作的结果,作为参数传递出去;reject函数的作⽤是,将Promi对象的状态从“未完成”变为“失败”(即从pending变为rejected),在异步
操作失败时调⽤,并将异步操作报出的错误,作为参数传递出去。
Promi实例⽣成以后,可以⽤then⽅法分别指定resolved状态和rejected状态的回调函数。
(function(value){
//success
},function(error){
//failure
});
then⽅法可以接受两个回调函数作为参数。第⼀个回调函数是Promi对象的状态变为resolved时调⽤,第⼆个回调函数是Promi对象的状
态变为rejected时调⽤。其中,第⼆个函数是可选的,不⼀定要提供。这两个函数都接受Promi对象传出的值作为参数。
下⾯是⼀个Promi对象的简单例⼦。
functiontimeout(ms){
returnnewPromi((resolve,reject)=>{
tTimeout(resolve,ms,'done');
});
}
timeout(100).then((value)=>{
(value);
});
上⾯代码中,timeout⽅法返回⼀个Promi实例,表⽰⼀段时间以后才会发⽣的结果。过了指定的时间(ms参数)以后,Promi实例的状
态变为resolved,就会触发then⽅法绑定的回调函数。
Promi新建后就会⽴即执⾏。
letpromi=newPromi(function(resolve,reject){
('Promi');
resolve();
});
(function(){
('resolved.');
});
('Hi!');
//Promi
//Hi!
//resolved
上⾯代码中,Promi新建后⽴即执⾏,所以⾸先输出的是Promi。然后,then⽅法指定的回调函数,将在当前脚本所有同步任务执⾏完
才会执⾏,所以resolved最后输出。
下⾯是异步加载图⽚的例⼦。
functionloadImageAsync(url){
returnnewPromi(function(resolve,reject){
constimage=newImage();
=function(){
resolve(image);
};
r=function(){
reject(newError('Couldnotloadimageat'+url));
};
=url;
});
}
上⾯代码中,使⽤Promi包装了⼀个图⽚加载的异步操作。如果加载成功,就调⽤resolve⽅法,否则就调⽤reject⽅法。
Promi是⼀个构造函数,⾃⼰⾝上有all、reject、resolve这⼏个眼熟的⽅法,原型上有then、catch等同样很眼熟的⽅法。
那就new⼀个
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
('执⾏完成');
resolve('随便什么数据');
},2000);
});
Promi的构造函数接收⼀个参数,是函数,并且传⼊两个参数:resolve,reject,分别表⽰异步操作执⾏成功后的回调函数和异步操作执
⾏失败后的回调函数。其实这⾥⽤“成功”和“失败”来描述并不准确,按照标准来讲,resolve是将Promi的状态置为fullfiled,reject是将
Promi的状态置为rejected。不过在我们开始阶段可以先这么理解,后⾯再细究概念。
在上⾯的代码中,我们执⾏了⼀个异步操作,也就是tTimeout,2秒后,输出“执⾏完成”,并且调⽤resolve⽅法。
运⾏代码,会在2秒后输出“执⾏完成”。注意!我只是new了⼀个对象,并没有调⽤它,我们传进去的函数就已经执⾏了,这是需要注意的⼀
个细节。所以我们⽤Promi的时候⼀般是包在⼀个函数中,在需要的时候去运⾏这个函数,如:
functionrunAsync(){
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
('执⾏完成');
resolve('随便什么数据');
},2000);
});
returnp;
}
runAsync()
这时候你应该有两个疑问:1.包装这么⼀个函数有什么⽤?e('随便什么数据');这是⼲⽑的?
我们继续来讲。在我们包装好的函数最后,会return出Promi对象,也就是说,执⾏这个函数我们得到了⼀个Promi对象。还记得
Promi对象上有then、catch⽅法吧?这就是强⼤之处了,看下⾯的代码:
runAsync().then(function(data){
(data);
//后⾯可以⽤传过来的数据做些其他操作
//......
});
在runAsync()的返回上直接调⽤then⽅法,then接收⼀个参数,是函数,并且会拿到我们在runAsync中调⽤resolve时传的的参数。运⾏这段
代码,会在2秒后输出“执⾏完成”,紧接着输出“随便什么数据”。
这时候你应该有所领悟了,原来then⾥⾯的函数就跟我们平时的回调函数⼀个意思,能够在runAsync这个异步任务执⾏完成之后被执⾏。这
就是Promi的作⽤了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执⾏完后,⽤链式调⽤的⽅式执⾏回调函数。
你可能会不屑⼀顾,那么⽜逼轰轰的Promi就这点能耐?我把回调函数封装⼀下,给runAsync传进去不也⼀样吗,就像这样:
functionrunAsync(callback){
tTimeout(function(){
('执⾏完成');
callback('随便什么数据');
},2000);
}
runAsync(function(data){
(data);
});
效果也是⼀样的,还费劲⽤Promi⼲嘛。那么问题来了,有多层回调该怎么办?如果callback也是⼀个异步操作,⽽且执⾏完后也需要有
相应的回调函数,该怎么办呢?总不能再定义⼀个callback2,然后给callback传进去吧。⽽Promi的优势在于,可以在then⽅法中继续写
Promi对象并返回,然后继续调⽤then来进⾏回调操作。
链式操作的⽤法
所以,从表⾯上看,Promi只是能够简化层层回调的写法,⽽实质上,Promi的精髓是“状态”,⽤维护状态、传递状态的⽅式来使得回调
函数能够及时调⽤,它⽐传递callback函数要简单、灵活的多。所以使⽤Promi的正确场景是这样的:
runAsync1()
.then(function(data){
(data);
returnrunAsync2();
})
.then(function(data){
(data);
returnrunAsync3();
})
.then(function(data){
(data);
});
这样能够按顺序,每隔两秒输出每个异步回调中的内容,在runAsync2中传给resolve的数据,能在接下来的then⽅法中拿到。运⾏结果如
下:
猜猜runAsync1、runAsync2、runAsync3这三个函数都是如何定义的?没错,就是下⾯这样
functionrunAsync1(){
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
('异步任务1执⾏完成');
resolve('随便什么数据1');
},1000);
});
returnp;
}
functionrunAsync2(){
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
('异步任务2执⾏完成');
resolve('随便什么数据2');
},2000);
});
returnp;
}
functionrunAsync3(){
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
('异步任务3执⾏完成');
resolve('随便什么数据3');
},2000);
});
returnp;
}
在then⽅法中,你也可以直接return数据⽽不是Promi对象,在后⾯的then中就可以接收到数据了,⽐如我们把上⾯的代码修改成这样:
runAsync1()
.then(function(data){
(data);
returnrunAsync2();
})
.then(function(data){
(data);
return'直接返回数据';//这⾥直接返回数据
})
.then(function(data){
(data);
});
那么输出就变成了这样:
reject的⽤法
到这⾥,你应该对“Promi是什么玩意”有了最基本的了解。那么我们接着来看看ES6的Promi还有哪些功能。我们光⽤了resolve,还没⽤
reject呢,它是做什么的呢?事实上,我们前⾯的例⼦都是只有“执⾏成功”的回调,还没有“失败”的情况,reject的作⽤就是把Promi的状态
置为rejected,这样我们在then中就能捕捉到,然后执⾏“失败”情况的回调。看下⾯的代码。
functiongetNumber(){
varp=newPromi(function(resolve,reject){
//做⼀些异步操作
tTimeout(function(){
varnum=(()*10);//⽣成1-10的随机数
if(num<=5){
resolve(num);
}
el{
reject('数字太⼤了');
}
},2000);
});
returnp;
}
getNumber()
.then(
function(data){
('resolved');
(data);
},
function(reason,data){
('rejected');
(reason);
}
);
getNumber函数⽤来异步获取⼀个数字,2秒后执⾏完成,如果数字⼩于等于5,我们认为是“成功”了,调⽤resolve修改Promi的状态。否
则我们认为是“失败”了,调⽤reject并传递⼀个参数,作为失败的原因。
运⾏getNumber并且在then中传了两个参数,then⽅法可以接受两个参数,第⼀个对应resolve的回调,第⼆个对应reject的回调。所以我们
能够分别拿到他们传过来的数据。多次运⾏这段代码,你会随机得到下⾯两种结果:
catch的⽤法
我们知道Promi对象除了then⽅法,还有⼀个catch⽅法,它是做什么⽤的呢?其实它和then的第⼆个参数⼀样,⽤来指定reject的回调,
⽤法是这样:
getNumber()
.then(function(data){
('resolved');
(data);
})
.catch(function(reason){
('rejected');
(reason);
});
效果和写在then的第⼆个参数⾥⾯⼀样。不过它还有另外⼀个作⽤:在执⾏resolve的回调(也就是上⾯then中的第⼀个参数)时,如果抛出
异常了(代码出错了),那么并不会报错卡死js,⽽是会进到这个catch⽅法中。请看下⾯的代码:
getNumber()
.then(function(data){
('resolved');
(data);
(somedata);//此处的somedata未定义
})
.catch(function(reason){
('rejected');
(reason);
});
在resolve的回调中,我们(somedata);⽽somedata这个变量是没有被定义的。如果我们不⽤Promi,代码运⾏到这⾥就直接在
控制台报错了,不往下运⾏了。但是在这⾥,会得到这样的结果:
也就是说进到catch⽅法⾥⾯去了,⽽且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相
同的功能。
all的⽤法
Promi的all⽅法提供了并⾏执⾏异步操作的能⼒,并且在所有异步操作执⾏完后才执⾏回调。我们仍旧使⽤上⾯定义好的runAsync1、
runAsync2、runAsync3这三个函数,看下⾯的例⼦:
Promi
.all([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
(results);
});
⽤来执⾏,all接收⼀个数组参数,⾥⾯的值最终都算返回Promi对象。这样,三个异步操作的并⾏执⾏的,等到它们都执⾏完
后才会进到then⾥⾯。那么,三个异步操作返回的数据哪⾥去了呢?都在then⾥⾯呢,all会把所有异步操作的结果放进⼀个数组中传给
then,就是上⾯的results。所以上⾯代码的输出结果就是:
有了all,你就可以并⾏执⾏多个异步操作,并且在⼀个回调中处理所有的返回数据,是不是很酷?有⼀个场景是很适合⽤这个的,⼀些游戏
类的素材⽐较多的应⽤,打开⽹页时,预先加载需要⽤到的各种资源如图⽚、flash以及各种静态⽂件。所有的都加载完后,我们再进⾏页⾯
的初始化。
race的⽤法
all⽅法的效果实际上是「谁跑的慢,以谁为准执⾏回调」,那么相对的就有另⼀个⽅法「谁跑的快,以谁为准执⾏回调」,这就是race⽅
法,这个词本来就是赛跑的意思。race的⽤法与all⼀样,我们把上⾯runAsync1的延时改为1秒来看⼀下:
Promi
.race([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
(results);
});
这三个异步操作同样是并⾏执⾏的。结果你应该可以猜到,1秒后runAsync1已经执⾏完了,此时then⾥⾯的就执⾏了。结果是这样的:
你猜对了吗?不完全,是吧。在then⾥⾯的回调开始执⾏时,runAsync2()和runAsync3()并没有停⽌,仍旧再执⾏。于是再过1秒后,输出了
他们结束的标志。
这个race有什么⽤呢?使⽤场景还是很多的,⽐如我们可以⽤race给某个异步请求设置超时时间,并且在超时后执⾏相应的操作,代码如
下:
//请求某个图⽚资源
functionrequestImg(){
varp=newPromi(function(resolve,reject){
varimg=newImage();
=function(){
resolve(img);
}
='xxxxxx';
});
returnp;
}
//延时函数,⽤于给请求计时
functiontimeout(){
varp=newPromi(function(resolve,reject){
tTimeout(function(){
reject('图⽚请求超时');
},5000);
});
returnp;
}
Promi
.race([requestImg(),timeout()])
.then(function(results){
(results);
})
.catch(function(reason){
(reason);
});
requestImg函数会异步请求⼀张图⽚,我把地址写为"xxxxxx",所以肯定是⽆法成功请求到的。timeout函数是⼀个延时5秒的异步操作。我
们把这两个返回Promi对象的函数放进race,于是他俩就会赛跑,如果5秒之内图⽚请求成功了,那么遍进⼊then⽅法,执⾏正常的流程。
如果5秒钟图⽚还未成功返回,那么timeout就跑赢了,则进⼊catch,报出“图⽚请求超时”的信息。运⾏结果如下:
本文发布于:2022-12-27 03:37:05,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/37856.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |