Js代码是单线程运行,这就意味着类似于网络请求/数据库查询等耗时操作,需要放在异步进行,返回结果通过回调函数实现。在js回调函数是这样子:
$jquery.get(‘http://www.baidu.com/’, function(data, status) {
});
如果有这样的一个需求,在请求登陆接口成功后,再请求获取当前的用户信息,那么网络请求可能会写成这样:
$jquery.post(‘/login’, function(data, status) {
if(status === 200) {
$jquery.get(‘/memberInfo?id=1’, function(data, status) {
……
})
}
})
上述代码中,我们需要在第一个请求的回调中,判断请求结果,然后再进行第二请求,如果还有需求需要更多的请求,那么上述代码可能会变成这样子:
$jquery.post(.., function(){
$jquery.post(.., function(){
$jquery.post(.., function(){
$jquery.post(.., function(){
……
})
})
})
})
很多的callback嵌套,我们简称回调地狱。Promise的出现,可以解决这个问题。使用promise后,回调地狱可以变为这样子:
$jquery.post(‘address1’)
.then(function(data, status) {
// 业务处理
return $jquery.post(‘address2’);
})
.then(function(data, status) {
return $jquery.post(‘address3’);
})
.then(function(data, status) {
return $jquery.post(‘addres4’);
})
.catch(function(err) {
})
Promise是一个代表了异步操作最终完成或者失败的结果对象。它本质上是一个绑定了回调的对象,而不是将回调传进函数内部。常见的promise调用方式,以axios调用举例:
axios.get(‘http://www.baidu.com’)
.then(function(res) {
console.log(res);
})
.catch(function(err) {
console.log(err);
})
上述get方法在axios库的内部,其实已经将promise封装好了,所以看不到promise相关的单词。那么我们如何创建promise函数,可以如同上述代码那样调用呢?
以封装一个ajax的get请求为例:
function get(address) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open('GET', address, true);
request.send();
});
}
首先需要在函数体里面创建Promise对象并返回,一般来说是这样的形式: new Promise(function (resolve, reject) {}); 回调函数中有两个参数:resolve, reject。resolve表示程序运行完成,得到正确结果;reject表示程序运行过程中出现错误。然后上述ajax请求promise封装,调用方式:
get('http://www.baidu.com/‘)
.then(function(res){
// 响应的字符串 request.responseText
})
.catch(function(e){
// 错误 request.status
});
分析:在ajax的异步请求回调里,我们先对请求响应进行判断,如果成功,使用resolve返回结果,它将在.then()函数回调中得到这个结果,如果失败,使用reject返回失败原因,它将在.catch()函数回调中得到原因。
resolve和reject函数,实际上都是返回promise对象。但是它们内部的状态不一样,分别是:resolve promise和reject promise。而第一个.then能够执行的条件是,“前面”的promise对象中执行了resolve方法。我们可以验证一下:
function get(address) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
// 此处将resolve方法注释
// resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open('GET', address, true);
request.send();
});
}
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
});
结果:
// 无
结果没有任何输出,程序无法进入then里,我们把上面的函数改一下:
function get(address) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
resolve(request.responseText);
} else {
reject(request.status);
}
}
};
request.open('GET', address, true);
request.send();
});
}
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
});
结果:
then1 <!DOCTYPE html>。。。。。。
then2 undefined
可以看到第一个then里有回调结果,而第二个then也执行了,但是回调结果是undefined。**通过 .then 形式添加的回调函数,甚至都在异步操作完成之后才被添加的函数,都会被调用,如上所示。**可想而之,.then方法会一直执行下去。如果需要第二个.then拥有回调结果,需要前面的.then返回一个对象或者值。可以验证一下:
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
get('http://www.bing.com/'); // 不使用return返回
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
});
结果:
then1 <!DOCTYPE html>。。。。。。
then2 undefined
将resolve promise对象使用return返回:
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
return get('http://www.bing.com/'); // 不使用return返回
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
});
结果:
then1 <!DOCTYPE html>。。。。。。
then2 <!DOCTYPE html>。。。。。。
当程序运行错误,需要返回reject promise,当程序reject的时候,会直接被.catch()函数捕获错误,而不会进入到.then()函数。
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
return Promise.reject('1');
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
});
结果
then1 <!DOCTYPE html>。。。。。。
reject 1
当reject进入.catch()函数后,可以继续使用链式操作,类似于Java的try catch后的finally,例如:
get('http://www.baidu.com/')
.then(function(res){
console.log('then1', res);
return Promise.reject('1');
})
.then(function(res){
console.log('then2', res);
})
.catch(function(e){
console.log('reject', e);
})
.then(function(){
console.log('finally');
});
结果:
then1 <!DOCTYPE html>。。。。。。
reject 1
finally
Promise.all() 和 Promise.race()是并行运行异步操作的两个组合式工具。
Promise.all()接受Promise对象组成的数组作为参数,它的返回参数也是数组。当promise的全部为resolve后,它才会进入.then()方法,只要有一个promise为reject,它就会进入.catch()。实现如下:
function get(address) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
resolve('resolve');
} else {
reject(request.status);
}
}
};
request.open('GET', address, true);
request.send();
});
}
function r() {
return new Promise(function(resolve,reject){
reject('reject');
})
}
Promise.all([get('http://www.baidu.com/'), get('http://www.bing.com/'), r()])
.then(function(res){
console.log(JSON.stringify(res));
})
.catch(function(e){
console.log('catch');
})
结果:
catch
将reject去掉:
Promise.all([get('http://www.baidu.com/'), get('http://www.bing.com/')])
.then(function(res){
console.log(JSON.stringify(res));
})
.catch(function(e){
console.log('catch');
})
结果:
["resolve", "resolve"]
Promise.race()接受的参数与Promise.all()一样,不同的是,它会辨别最快达到resolve或者reject的promise对象,如果这个最快是resolve,则进入.then()方法,如果是reject,则进入.catch()方法。
function get(address) {
return new Promise(function(resolve, reject){
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(request.readyState === 4) {
if(request.status === 200) {
resolve('resolve');
} else {
reject(request.status);
}
}
};
request.open('GET', address, true);
request.send();
});
}
function r() {
return new Promise(function(resolve,reject){
setTimeout(function() {
reject('reject');
}, 5000);
})
}
Promise.race([get('http://www.baidu.com/'), get('http://www.bing.com/'), r()])
.then(function(res){
console.log(JSON.stringify(res));
})
.catch(function(e){
console.log('catch');
})
结果:
"resolve"
将reject延时函数改为立即:
function r() {
return new Promise(function(resolve,reject){
setTimeout(function() {
reject('reject');
}, 0);
})
}
Promise.race([get('http://www.baidu.com/'), get('http://www.bing.com/'), r()])
.then(function(res){
console.log(JSON.stringify(res));
})
.catch(function(e){
console.log('catch');
})
结果:
catch
async函数实际是返回一个promise对象,如果在async函数return某值,实际会被包装为promise的_value值,状态为resolve。
await可以让JavaScript进行等待,它会等待一个promise执行并返回它的结果,JavaScript才会继续往下执行。
如上面的get()请求方法,我们可以这样写:
async function asy() {
try {
let respond = await get('http://www.baidu.com/');
} catch(e) {
console.log('当get方法中执行reject时')
}
}
respond的赋值会等待get方法中执行resolve,如果get方法里执行了reject,则会直接被catch函数捕捉。