什么是Promise
Promise是es6中提供的一个构造函数,是异步编程的一种解决方案。
为什么要用promise,在es6以前,又是怎么解决异步编程的呢?
首先,我们需要知道js是以单线程运行的,所以像网络操作、浏览器事件等都是异步执行的。
异步指的是发出一个功能调用之后,这个调用就直接返回了,所以没有返回结果。当这个调用完成后,一般通过状态、通知和回调来通知调用者。es6之前是使用了回调的方式通知调用者。现在也可以使用promise进行处理了。
假设现在有一个需求,先根据用户的id获取到用户的角色ID,然后根据用户的角色id,查询此用户能访问到的商品列表。
// ajax函数
function mockAjax(method, url, data, success, error) {
setTimeout(() => {
console.log('异步请求’);
var timeOut = Math.random() * 10;
if (timeOut < 5) {
const result = {status: 100, msg: ’success’, data: []}
success(result);
}
else {
error('接口地址错误');
}
},1000);
}
function getList(userId) {
mockAjax(‘GET', ‘/user/role?userId=' + userId, function success(data) {
mockAjax(‘GET', '/product/list?roleId=' + data.roleId, function success(data) {
// 逻辑处理
}, function error(error) {
// 错误处理
});
}, function error(error) {
// 错误处理
});
}
可以看到,这段代码的可读性不是很好,我们的例子只是嵌套了2层,如果是更多的嵌套又会是怎么样的呢?
promise主要是为了解决js中多个异步回调难以维护和控制的问题。
那么我们应该怎么用Promise函数呢?
我们可以在chrome浏览器上的控制台打印console.dir(Promise),查看Promise的具体组成。
从打印信息上可以看得出Promise其实是一个构造函数,Promise函数上还有有all,race, resolve,reject等静态方法
Promise函数上的prototype属性上有then,catch、finally方法,因此只要是Promise的实例,都可以调用Promise.prototype上面的方法(then,catch)。
既然是构造函数,那么我们就可以使用new操作符创建一个promise对象
let promise = new Promise();
在实例化Promise时需要传入一个执行函数作为参数,并且在创建对象时同步执行
let promise = new Promise(() => {
var timer = setTimeout(() => {
console.log('异步请求');
},1000);
});
运行这段代码,1s后,我们可以在控制台上看到输出的结果,这就说明在实例化过程中,作为参数的函数也会执行
执行函数实际上还有两个参数resolve和reject,其实这两个参数也是函数,下面我们学习一下resolve和reject的用法
let promise = new Promise((resolve, reject) => {
var timer = setTimeout(() => {
console.log('异步请求');
},1000);
});
我们先来学习resolve的用法
首先我们来看看Promise的几种状态:
pending: 初始状态,成功或失败状态。
fulfilled: 意味着操作成功完成。
rejected: 意味着操作失败。
当我们在执行函数中调用resolve方法时,Promise的状态就变成fulfilled,即操作成功状态,还记得上面Promise.prototype上面的then和catch方法吗?当Promise状态为fullfilled状态时执行then方法里的操作,注意了,then方法里面有两个参数onfulfilled(Promise为fulfilled状态时执行) 和onrejected(Promise为rejected状态时执行),步骤如下:
1,实例化Promise(new Promise(function(resolve,reject)))
2,用Promise的实例调用then方法
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
console.log('异步请求’);
const result = {status: 100, msg: ’success’, data: []}
resolve(result)
},1000);
});
promise.then(data => {
console.log(data);
})
在这个小例子中,在Promise的执行函数中的setTimeout1秒之后,执行resolve函数,这个promise的状态变成了fulfilled,然后执行了then方法里的第一个函数,也就是onfulfilled函数。其实then里面的函数就是我们平时所说的回调函数,只不过Promise将执行代码和处理结果的代码分离开来了。
那么通过上面的小例子,reject应该也就能很好的理解了,就是调用reject方法后,Promise状态变为rejected,即操作失败状态,此时执行then方法里面onrejected操作,也就是then方法中的第二个函数
let promise = new Promise((resolve, reject) => {
var timeOut = Math.random() * 2;
setTimeout(() => {
console.log('异步请求’);
if (timeOut < 1) {
const result = {status: 100, msg: ’success’, data: []}
resolve(result)
}
else {
reject(‘error');
}
},1000);
});
promise.then(data => {
console.log(data);
}, error => {
console.log(error);
})
之前我们打印Promise时,它的prototype属性、也就是原型上还有catch函数,这个函数又是做什么的呢?
catch方法的作用和then方法的第二个参数onrejected回调函数是一样的,当promise的状态变成了rejected时,就执行catch方法,实际上catch方法就是then方法的一个语法糖
let promise = new Promise((resolve, reject) => {
var timeOut = Math.random() * 2;
setTimeout(() => {
console.log('异步请求’);
if (timeOut < 1) {
const result = {status: 100, msg: ’success’, data: []}
resolve(result)
}
else {
reject('接口地址错误');
}
},1000);
});
promise.then(data => {
console.log(data);
})
promise.catch(error => {
console.log(error);
})
then函数也可以链式调用
promise.then(data => {
console.log(data);
}).catch(error => {
console.log(error);
})
promise原型上还有一个finally方法
在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。
promise.then(data => {
console.log(data);
}).catch(error => {
console.log(error);
}).finally(() => {
console.log('统一处理')
});
简单的Promise就讲完了,现在我们可以使用promise修改例子
// ajax函数将返回Promise对象:
function mockAjax(method, url, data) {
return new Promise(function (resolve, reject) {
var timeOut = Math.random() * 10;
setTimeout(() => {
console.log('异步请求', timeOut);
if (timeOut < 5) {
const result = {status: 100, msg: 'success', data: []}
resolve(result)
} else {
reject('接口地址错误');
}
},1000);
});
}
function getList(userId) {
mockAjax('GET', '/role?userId=' + userId)
.then(data => {
return mockAjax('GET', '/product/list?roleId=' + data.roleId);
})
.then(data => {
console.log('商品列表', data);
}).catch(error) => {
console.log('error', error);
});
}
getList(1);
从代码量上看,使用promise好像更多了,但是promise最大的优势就是将执行过程代码和结果处理的代码解藕了。增强了代码的可读性和可维护性。
上面这个例子是一个串行执行的任务,只有执行完第一个任务之后,才可以执行第二个任务。
有的时候,也需要并行执行异步任务,我们可以通过Promise的静态方法all来实现,比如说在一个页面需要同时展示用户信息和角色信息,并且需要等这两个接口都返回了值,才进行渲染
let getUser = mockAjax('GET', '/user?userId=' + 1);
let getRole = mockAjax('GET', '/role?userId=' + 1);
// 同时执行getUser和getRole,并在它们都完成后执行then:
Promise.all[getUser, getRole].then(results => {
console.log(‘results’, results);
}).catch(error => {
console.log(‘error’, error);
});
// Promise.all可以将多个Promise实例包装成一个新的Promise实例。成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。
还有的时候,我们同时发起多个请求,只需要获得先返回的结果即可,另外的请求都会停止。我们可以通过Promise.race静态方法写一个支持超时的异步请求。,race意思就是竞赛的意思
// ajax函数将返回Promise对象:
function mockAjax(method, url, data) {
return new Promise(function (resolve, reject) {
var timeOut = Math.random() * 10;
setTimeout(() => {
console.log('异步请求', timeOut);
if (timeOut < 5) {
const result = {status: 100, msg: 'success', data: []}
resolve(result)
} else {
reject('接口地址错误');
}
},1000);
});
}
function delay(ms = 500) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms)
})
}
function timeoutAjax(promise) {
var timeout = delay().then(function () {
throw new Error('请求超时');
});
return Promise.race([promise, timeout]);
}
let getUser = mockAjax('GET', '/user?userId=' + 1);
timeoutAjax(getUser).then(data => {
console.log('data', data);
}).catch(error => {
console.log('error', error)
})