回调地狱
就是说,在异步js里,当一个异步任务的执行需要依赖另一个异步任务的结果时,一般会将两个异步任务嵌套起来,如果嵌套的太多了,回调套回调,导致很难凭直觉看懂代码。
(所以这并不是一个多高深的词,不要被它的名字唬住了。)
什么是Promise
Promise是ES6新增的语法,是异步编程的一种解决方案,解决了回调地狱的问题。其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。
(说白了就是让异步代码看起来好懂。。) 例:
let p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('Promise执行完成');
resolve('任何数据');
}, 2000);
});
其执行过程是:执行了一个异步操作,也就是setTimeout,2秒后,输出“执行完成”,并且调用resolve方法。
Promise三种状态
Promise有三种状态,初始状态是pending,
可以通过函数resolve变为rejected状态,也可以通过resolve函数变为resolved状态,
状态一旦改变就不能再次变化(Promise规范规定除了pending状态,其他状态是不可以改变的)。
Promise用法
实际上Promise上的实例promise是一个对象,不是一个函数。在声明的时候,Promise传递的参数函数会立即执行,因此Promise使用的正确姿势是在其外层再包裹一层函数,在需要的时候去运行这个函数,如:
<div onClick={promiseClick}>开始异步请求</div>
const promiseClick =()=>{
console.log('点击方法被调用')
let p = new Promise(function(resolve, reject){
//做一些异步操作
setTimeout(function(){
console.log('执行完成Promise');
resolve('要返回的数据可以任何数据例如接口返回数据');
}, 2000);
});
return p;
}
executor(resolve,reject)
executor是带有 resolve 和 reject 两个参数的函数 。
Promise构造函数执行时立即调用executor 函数, resolve 和 reject 两个函数作为参数传递给executor(executor 函数在Promise构造函数返回新建对象前被调用)。
resolve 和 reject 函数被调用时,分别将promise的状态改为fulfilled(完成)或rejected(失败)。executor 内部通常会执行一些异步操作,一旦完成,可以调用resolve函数来将promise状态改成fulfilled,或者在发生错误时调用reject函数将它的状态改为rejected。
.then
每个Promise的实例对象,都有一个then的方法,这个方法就是用来处理之前各种异步逻辑的结果。
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用(成功时调用),接受的参数是Promise里resove()括号里的值,在下面代码中是num
;第二个回调函数是Promise对象的状态变为rejected时调用(失败时调用),参数是reject()括号里的值,在下面代码中是:'数字太于10了即将执行失败回调'
。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
function promiseClick(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
promiseClick().then(
function(data){
console.log('resolved成功回调');
console.log('成功回调接受的值:',data);
},
function(reason){
console.log('rejected失败回调');
console.log('失败执行回调抛出失败原因:',reason);
}
);
执行结果:
then链式调用
对于Promise的then()方法,then总是会返回一个Promise实例,因此你可以一直调用then,形如run().then().then().then().then().then()…
在一个then()方法调用异步处理成功的状态时,你既可以return一个确定的“值”,也可以再次返回一个Promise实例。
当返回的是一个确切的值的时候,then会将这个确切的值传入一个默认生成的Promise实例,并且这个Promise实例会立即置为fulfilled状态,以供接下来的then方法里使用。看代码:
let num = 0
let run = function() {
return new Promise(resolve => {
resolve(`${num}`)})
}
run().then(val => {
console.log(val)
return val
})
.then(val =>{
val++
console.log(val)
return val
})
.then(val =>{
val++
console.log(val)
})
// 输出: 0 1 2
catch
与Promise对象方法then方法并行的一个方法就是catch,与try catch类似,catch就是用来捕获异常的,也就是和then方法中接受的第二参数rejected的回调是一样的(换句话说,用不用都一样,用了代码更好理解),如下:
function promiseClick(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
promiseClick().then(
function(data){
console.log('resolved成功回调');
console.log('成功回调接受的值:',data);
}
)
.catch(function(reason, data){
console.log('catch到rejected失败回调');
console.log('catch失败执行回调抛出失败原因:',reason);
});
all用法
与then同级的另一个方法,all方法接收一个Promise数组参数。数组里的所有Promise都执行了resolve(都成功执行完了)才会调用then,并将几个Promise的resolve返回值存在一个数组中传给then做参数。这里的results就是一个结果数组。如果有一个Promise执行失败,调用了reject函数,则会控制台报错,报错内容为reject的内容。
有一个场景是很适合用all的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
Promise.all([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log(results);
});
执行结果:
race用法
all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调。
function promiseClick1(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('1s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('1s数字太于10了即将执行失败回调');
}
}, 1000);
})
return p
}
function promiseClick2(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('2s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('2s数字太于10了即将执行失败回调');
}
}, 2000);
})
return p
}
function promiseClick3(){
let p = new Promise(function(resolve, reject){
setTimeout(function(){
var num = Math.ceil(Math.random()*20); //生成1-10的随机数
console.log('3s随机数生成的值:',num)
if(num<=10){
resolve(num);
}
else{
reject('3s数字太于10了即将执行失败回调');
}
}, 3000);
})
return p
}
Promise
.race([promiseClick3(), promiseClick2(), promiseClick1()])
.then(function(results){
console.log('成功',results);
},function(reason){
console.log('失败',reason);
});
执行结果:
真实场景代码举例
// 执行搜索帮助接口,获取数据
const getShTableData = val => {
const { table, initialValue = {} } = searchHelper;
return new Promise(async (resolve, reject) => {
if (process.env.MOCK_DATA === 'true') {
await sleep(500);
const { data } = cloneDeep(require('@/mock/tableData').default);
return resolve(data.items);
} else {
const params = merge({}, table.fetch?.params, formatParams({ ...initialValue, ...createShFilters(val) }), { currentPage: 1, pageSize: 500 });
try {
const res = await table.fetch.api(params);
if (res.code === 200) {
const list = get(res.data, table.fetch.dataKey) ?? (Array.isArray(res.data) ? res.data : []);
return resolve(list);
}
} catch (err) {}
reject();
}
});
};
getShTableData(val)
.then(list => resetSearchHelperValue(list, val))
.catch(() => clearSearchHelperValue(!0));
手写Promise
当面试官认为你可能是个大佬,通常会让你手写一个Promise来验证你值不值一个高工资。
var a=1;
const PENDING='pending';
const RESOLVED='resolved';
const REJECTED='rejected';
function myPromise(fn){
const that=this;
that.state=PENDING;
that.resolvedCallbackArray=[];
that.rejectedCallbackArray=[];
function resolve(value){
that.state=RESOLVED;
that.resolvedCallbackArray.forEach(cbFn=>cbFn(value));
}
function reject(value){
that.state=REJECTED;
that.rejectedCallbackArray.forEach(cbFn=>cbFn(value))
console.log('reject调用',value)
}
try{
fn(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFulfilled,onRejected){
const that=this;
that.resolvedCallbackArray.push(onFulfilled);
that.rejectedCallbackArray.push(onRejected);
return that;}
function f(){
return new myPromise((resolve,reject)=>{
if(a===1){
setTimeout(()=>{
resolve('成功');
},2000)
}else{
reject('失败')
}
})
}
f().then(value=>{
console.log('第一次',value)
})
执行结果: