在JavaScript中代码都是单线程执行的,因此JavaScript中所有的网络操作、浏览器事件都必须异步执行。在Promise
之前JavaScript处理异步的方式都是回调函数。可以说callback
的方式已是深入人心,那Promise
又是解决什么问题的呢?看下面一段代码:
$.get('/getList',function(){
$.get('/getCount',function(){
$.get('/getDetail',function(){
//....
})
})
})
这段代码就是传统的callback
方式处理异步,可以看到刚刚3级嵌套代码层级就已经比较乱了,如果再加上一些逻辑代码那简直是无法直视。这就是我们常说的回调地狱问题。代码可读性低,难以维护,无法复用。
Promise解决回调地狱
Promise
的基本用法
var promise = new Promise((resolve,reject)=>{
setTimeout(function(){
//这里异步操作已经执行完了,就可以通过resolve告诉外界可以进行其他操作了
resolve('ok');
//reject('no');
},2000);
})
promise.then(res=>{
console.log(res); // ok
},err=>{
console.log(err); // no
})
通过Promise
处理异步,先执行异步操作,不关心如何处理结果,通过Promise
对象的返回成功还是失败,在将来的某个时刻执行结果处理函数。代码变得扁平化,且易读易维护。
-
resolve && reject
上面代码我们通过 resolve 方法把 Promise 的状态置为完成态(Resolved),这时 then 方法就能捕捉到变化,并执行“成功”情况的回调。
而 reject 方法就是把 Promise 的状态置为已失败(Rejected),这时 then 方法执行“失败”情况的回调(then 方法的第二参数)。
Promise实现多层回调
同样三级回调的代码我们再使用Promise
重构一遍
new Promise((resolve,reject)=>{
$.get('/getList',res=>{
resolve(res);
});
}).then(res=>{
return new Promise((resolve,reject)=>{
$.get('/getCount',res=>{
resolve(res);
});
});
}).then(res=>{
return new Promise((resolve,reject)=>{
$.get('/getDetail',res=>{
resolve(res);
})
});
}).then(res=>{
//...
});
可以看到无论有多少层回调,都不用互相嵌套,只需要等待Promise
对象“通知“执行即可。
Promise.all
当需要进行多步没有关联逻辑的异步操作时,可以使用Promise.all
Promise.all([
$.get('/getList'),
$.get('/getCount'),
$.get('/getDetail')
]).then(([data1,data2,data3])=>{
//then回调的参数是一个数组,顺组数序就是异步操作书写顺序
console.log(data1,data2,data3);
},err=>{
console.log(err);
});
-
all
方法中的参数应该是Promise
对象,因为ajax
函数返回的对象就是promise对象所以这里是可以执行的; - 此种方式只适用于异步操作之间无逻辑关联;
- 不论异步操作执行顺序如何,最后都会按照书写顺序返回(
data1,data2,data3
是按照异步操作书写顺序返回); - 如果如其中一个出错就会执行错误回调;
Promise.race
race
的用法与all
相似,不同点就是all
会等所有异步操作全部执行完后再执行then回调,而race
中只要有一个异步操作执行完成就立刻执行then回调。
Promise.race([
new Promise(function(resolve, reject){
setTimeout(function(){
console.log('函数一执行!');
resolve('11');
}, 1000);
}),
new Promise(function(resolve, reject){
setTimeout(function(){
console.log('函数二执行!');
resolve('22');
}, 1200);
})
]).then(result=>{
console.log('then执行');
console.log(result);
},err=>{
console.log(err);
});
//执行结果为:
//函数一执行!
//then执行
//11
//函数二执行!
可以看到函数一执行明显要比函数二快,所以执行了函数一后立即执行了then回调,注意这时函数二依然会执行,但是执行后不会再触发then回调的执行。
Promise错误处理
- 第一种方式,通过
then
。
new Promise((resolve,reject)=>{
//...
}).then(resSuccess=>{
//成功
},resError=>{
//失败
});
- 第二种方式,
catch
捕获
new Promise((resolve,reject)=>{
//...
}).then(resSuccess=>{
//成功
}).catch(resError=>{
//失败
});
注:catch
方式更常用,因为不仅仅能捕获到reject传递的参数,还可以捕获到成功的回调中发生的错误。
(*Deferred对象及其方法)
jQuery 用 $.Deferred 实现了 Promise 规范。
function fn1() {
var def = $.Deferred();
//执行异步操作
setTimeout(function () {
console.log('函数1!');
def.resolve('函数1执行完毕回调');
//def.reject('函数1执行完毕回调');
}, 1000);
return def.promise();
}
//then方法第一个参数接收成功回调,第二个参数是接收失败回调
fn1().then(res => {
console.log(res);
},err=>{
console.log(err);
});
$.Deferred()
方法返回一个对象,我们可以称之为Deferred
对象,该对象包含一些方法如:then、done、fail 等。
jQuery
就是用这个Deferred
对象来实现Promise
规范的。
对于多级回调的情况也可以使用then()
方法进行链式调用:
function fn1() {
var def = $.Deferred();
//执行异步操作
setTimeout(function () {
console.log('执行函数1!');
def.resolve('函数1执行完毕回调');
}, 1000);
return def.promise();
};
function fn2() {
var def = $.Deferred();
//执行异步操作
setTimeout(function () {
console.log('执行函数2!');
def.resolve('函数2执行完毕回调');
}, 1000);
return def.promise();
};
fn1().then(res => {
console.log(res);
//可以链式调用的核心在于返回一个Deferred对象
return fn2();
}).then(res => {
console.log(res);
});
//执行函数1!
//函数1执行完毕回调
//执行函数2!
//函数2执行完毕回调
done() && fail()
done与fail的用法与then相似,实际就是一个语法糖。
function fn1() {
var def = $.Deferred();
setTimeout(function () {
console.log('执行函数1!');
def.resolve('函数1执行完毕回调');
// def.reject('函数1执行失败回调');
}, 1000);
return def.promise();
};
fn1().then(function (res) {
console.log(res);
}, function (err) {
console.log(err);
});
fn1().done(function (res) {
console.log(res);
}).fail(function (err) {
console.log(err);
});
always()
Defferred
对象上还有一个always方法
,无论异步操作返回什么结果都会执行always
回调。
function fn1() {
var def = $.Deferred();
setTimeout(function () {
console.log('执行函数1!');
def.resolve('函数1执行完毕回调');
// def.reject('函数1执行失败回调');
}, 1000);
return def.promise();
};
fn1().then(function (res) {
console.log(res);
}, function (err) {
console.log(err);
}).always(() => {
console.log('无论成功失败都会进入这里');
});
由于Promise的出现,jQuery的Deferred对象已经很少使用了。一些其他用法在这也不一一举例了,如果有兴趣可以去官网详细了解。
网友评论