Promise是什么
JS就是操作对象上的属性和方法,对于一个对象,想要了解,我们可以直接从其身上的属性和方法入手;直接使用console.dir(对象)打印出来
从上面打印出来的属性和方法,可以看到Promise是一个构造函数,有属于自己私有的all,reject,resolve,rece等方法,也有原型上面的,属于实例对象调用的方法then,catch
// Promise里面传入一个函数类型的参数,这个函数类型的参数接收两个参数resolve reject
var p=new Promise(function(resolve,reject){
// 异步操作
setTimeout(function(){
console.log('icessun'); // 两秒之后打印出icessun
resolve('icessun2'); // resolve是成功后的回调函数 里面的icessun2是传入的参数
},2000)
});
// 那么p是一个实例对象,可以使用then方法(Promise原型上面的方法)
p.then(function(){
console.log(arguments); // 会打印出一个类数组 ['icessun2']
})
p.then(function(data){
console.log(data); // 会打印出icessun2 data接收了resolve里面的参数
})
对于上面这段代码,首先new一个实例对象赋值给p,Promise的构造函数接受一个参数,是函数;并且传入两个参数:resolve,reject,分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数;然后里面设置一个定时器setTimeout,开启一个异步操作,两秒后输出icessun,并且调用resolve方法,注意一个细节:
上面的代码,只是new了一个对象实例,并没有调用,就执行了;对于这个情况,一般是把其嵌套在一个函数里面,避免立即执行,在需要的时候去运行这个函数。
p 是一个实例对象,可以使用then方法,其里面的函数是对于resolve或者reject的调用的体现,可以接收resolve,reject传入的参数
function icessun(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('icessun');
reslove('icessun2');
},2000);
});
return p; // 返回p实例,使其可以使用Promise原型上面的方法
}
icessun(); // 调用执行icessun函数 得到一个Promis对象
// 也可以直接这样调用
icessun().then(function(data){
console.log(data); // icessun2
// 一些其他的操作
// .....
});
通过上面的代码,知道then里面的函数就是经常说的回调函数callback,在icessun这个异步任务执行完成后被执行。把回调函数写法分离出来,在异步操作执行完后,用链式调用的方法执行回调函数,对于多层回调来说,非常的方便,可以继续在then的方法中继续写Promise对象并返回,继续调用then来进行回调操作,这就是Promise的作用。
链式操作
从上面看,Promise对于多层回调,可以简化其写法,使得更加的语义化;但是Promise的精髓在于其链式操作,传递状态,维护状态的方式使得回调函数能够及时的调用。打开Promise的正确场景是这样:
function runAsync1(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('执行完成1')
resolve('icessun1');
},2000);
});
return p; // 返回p实例对象
}
function runAsync2(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('执行完成2')
resolve('icessun2');
},2000);
});
return p; // 返回p实例对象
}
function runAsync3(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
console.log('执行完成3')
resolve('icessun3');
},2000);
});
return p; // 返回p实例对象
}
// 正确的打开Promise的方法
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
})
这样能够按照顺序,每隔两秒输出每个异步回调中的内容,运行结果:
当然我们可以直接return数据而不是Promise对象,在后面的then方法就可以直接接收到数据,如下:
// 正确的打开Promise的方法
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '我是直接返回的数据';
})
.then(function(data){
console.log(data);
})
reject的用法
前面我们说了resolve是执行成功的回调,那么reject就是执行失败的回调,将Promise的状态设置为rejected,这样就可以在then里面获取到,执行失败情况下的回调。
function getNumber(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
var num=Math.ceil(Math.random()*10); // 生成1-10 之间的随机数 Math.ceil(): 大于或等于给定数字的最小整数
if(num<=5){
resolve(num);
}else{
reject('数字太大了')
}
},2000);
});
return p;
}
getNumber()
.then(function(data){
console.log('resolved');
console.log(data);
},function(reason,data){
console.log('resolved');
console.log(reason); // 数字太大
console.log(data); // undefined
});
getNumber()函数执行后会出现两种情况,要么大于5,要么小于5,在then中传入了两个参数,第一个是对应resolve的回调,第二个是对应reject的回调。
catch的用法
看到这个方法,就会想到浏览器处理异常的try…catch()方法,有错误进入catch方法,不阻断程序的执行,其实这个方法也是来处理错误的,用来指定reject的回调,防止程序错误,阻断后面程序的执行,使其能够继续执行。
getNumber()
.then(function(data){
console.log('resolve');
console.log(data);
})
.catch(function(data){
console.log('reject');
console.log(data);
})
其效果和上面在then里面写两个函数是一样的,这个写法的好处是当代码出现了错误的时候,不会阻断程序的执行,而是进入catach方法。
all方法的使用
Promise对象上的方法,实例不能使用,只能这个对象使用,这个方法通过了并行执行异步操作的能力,并且在所有的异步操作完成后才执行回调
Promise
.all([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
console.log(results);
});
Promise.all来执行前面的三个异步的函数,all()接收一个数组参数,里面的执行最终都返回Promise对象,只有等三个异步操作都执行完成后才会进入到then里面,all会把所有的异步操作的结果放在一个数组中传给then,就是上面的results,代码的输出结果:
有了all,可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,一个常用的场景:游戏类的素材比较多的应用,打开网页的时候,预先加载需要用到的各种资源,如图片,flash以及各种静态文件,等到所有都加载完成,我们再进行页面的初始化。
race的用法
这个也是Promise类上面的私有方法,对于前面的all方法来说是:谁的程序执行的慢,就等谁执行完才回调。但是对于race来说:谁的程序执行的快,就以它为标准调用回调函数,其用法基本上是一样的,把上面runAsync1函数的延迟改为1秒
Promise
.race([runAsync1(),runAsync2(),runAsync3()])
.then(function(results){
console.log(results);
});
这三个 异步操作同样是并行执行的,但是等到1秒后,runAsync1已经执行完毕,于是then接受到了执行完毕的回调,输出回调结果;与此同时,runAsyn2和runAsyn3也继续执行,输出了执行的结果,但是不能回调then方法。
这个方法的使用场景很多,比如可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作:
// 请求某个图片资源 异步
function requestImg(){
var p=new Promise(function(resolve,reject){
var img=new Image(); // 创建一个图片对象实例 Image后面没有参数的时候,括号可以省略
img.src='xxxxx'; // 给对象上面的属性设置属性值
img.onload=function(){
resolve(img); // 图片成功加载的时候,把img对象作为参数传到回调函数里面
}
});
return p; // 当调用这个函数的时候可以使用then方法
}
// 延时函数 给请求计时
function timeout(){
var p=new Promise(function(resolve,reject){
setTimeout(function(){
reject('图片请求超时');
},4000);
});
return p;
}
Promise.race([requsetImg(),timeout()])
.then(function(results){
console.log(results); // 图片成功加载会把图片的路径打印在控制台
})
.catch(function(reason){
console.log(reason); // 失败会提示加载失败
})
requestImg函数会异步请求一张图片,图片地址写错,肯定是无法加载图片请求。timeout函数是一个延时4秒的异步操作,把这两个返回Promise对象的函数放到race里面,如果4秒内图片请求成功,就会回调then方法,执行正常的流程,否则进入catch方法,显示图片请求超时。