异步任务的处理
众所周知,新技术的出现都是为了解决旧技术的痛点。
function requestDate(url,successCallback,failCallback) {
// 用 setTimeout 来模拟网络请求
setTimeout(() => {
if (url === 'xxx/users') {
let successMessae = '请求成功';
successCallback(successMessae)
} else {
let failMessage = '请求失败';
failCallback(failMessage);
}
}, 2000);
}
// main.js,代表的是在另外的一个JS模块对 requestDate 进行调用
// 1 对于调用者来说,不论成功或者是失败都是要给一个相应的结果,
// 2 同时传入两个回调函数,在成功的时候执行回调函数一,在失败的时候执行第二个回调函数
requestDate('xxx/users',(res) => {
console.log(res);
},(err) => {
console.log(err);
});
小结:
- 首先我们先封装一个函数
requestDate,用于发送网络请求。 - 在调用
requestDate这个函数的时候,我们还需要传入两个回调函数successCallback和failCallback。 - 当请求成功的时候,我们执行第一个回调函数,当请求失败的时候,执行第二个回调函数,将成功和失败的值,通过回调函数传递出去。
弊端:
- 如果是我们自己封装的
requestDate,那么我们在封装的时候必须要自己设计好callBack函数的名称,并且要使用好。 - 如果我们使用的是别人所封装的
requestDate或者一些第三方的库,那么我们可能就需要去看源码或者文档,才知道回调函数的名称。
function requestDate2 () {
return '承诺';
}
const chengnuo = requestDate2();
console.log(chengnuo);
所谓的承诺,就是在成功的时候,告诉你成功了,失败的时候告诉你失败了,承诺(规范好了代码的编写逻辑)。而我们所说的Promise就是这样的一种规范。
什么是Promise?
Promise是一个类,可以翻译成为:承诺、许诺、契约。
- 当我们需要给予调用者一个承诺:待会儿我会给你回调数据时,就可以创建一个
Promise的对象。
在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor;
- 这个回调函数会被立即执行,并且这个回调函数,需要传入另外两个回调函数
resolve和reject; - 当我们调用
resolve回调函数时候,会执行Promise对象的then方法传入的回调函数。 - 当我们调用
reject回调函数时,会执行Promise对象的catch方法传入的回调函数;
Promise的状态
Promise在使用过程中,我们大概可以分为三个状态:
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝,当执行executor(执行器/执行者)中的代码时,处于该状态。
- 已兑现(fulfilled): 意味着操作成功完成,执行了resolve时,处于该状态。
- 已拒绝(rejected): 意味着操作失败,执行了reject时,处于该状态。
// 对于这个代码,是会直接进行调用的。
const f2 = new Promise(() => {
console.log('结束了');// 结束了
})
const f1 = new Promise((resolve,reject) => {
resolve('成功');
reject('失败');
});
f1.then((res) => {
console.log(res);// 成功
}).catch((err) => {
console.log(err); // 不会执行
});
要注意的是Promise的状态在改变之后,是不会继续发生改变的,所以当状态变为resolve状态后,下面调用reject是没有任何效果的。
当我们调用resolve的时候,外面的.then这个回调函数就会被执行,reject和.catch同样是如此。
Promise处理异步数据
function requestData(url) {
return new Promise((resolve,reject) => {
setTimeout(() => {
if (url === 'xxx.user/info') {
// 取到成功的结果
let data = '成功的消息';
resolve(data);
}else{
// 失败的结果
let errData = '错误的消息';
reject(errData);
}
},3000);
});
};
const r1 = requestData('xxx.user/info');
r1.then((res) => {
console.log(res);
});
// r1里面其实是可以放两个回调的。
r1.then((res) => {
//成功的回调
},(err) => {
// 失败的回调
})
对于异步请求的代码,我们都会放入到Promise的executor(执行器)里面,然后通过调用函数的resolve和reject两个办法,来将参数传递出去。
当然要记得.then里面是可以放两个回调的,第一个是成功的回调,第二个则是失败的回调。
resolve详解
1 直接调用resolve,并没有传递值的时候。
// promise 刚开始的状态叫做:pengding状态,或者称为待定状态。
const p1 = new Promise((resolve,reject) => {
console.log('promise开始');//会直接执行
resolve();
console.log('er');//这个代码也是会执行的,同样是同步代码。
});
p1.then((res) => {
console.log('res',res);// res undefined
},(err) => {
console.log('err',err);
})
首先,在Promise里面写的代码会直接执行,相当于是同步代码。然后当然我们直接调用resolve函数,并且不给他传递参数的时候,我们所接收到的res则是一个undefined值,而这个时候则是成功的状态(fulfilled),或者叫做已兑现的状态。
2 传递的是普通的值或者对象。
const p1 = new Promise((resolve,reject) => {
console.log('promise开始');//会直接执行
resolve({message:'cs2'});
});
p1.then((res) => {
console.log('res',res); // res { message: 'cs2' }
},(err) => {
console.log('err',err);
})
传入普通的值和对象,其实没有太大的区别,我们在接收的时候就能够发现,而数组同样也是如此。
3 传入一个Promise,那么当前Promise的状态,会由传入的Promise来进行决定。这个时候相当于将状态进行了一个移交。
const newPro= new Promise((resolve,reject) => {
});
const p1 = new Promise((resolve,reject) => {
console.log('----');//会执行
resolve(newPro);
console.log('e----');//会执行
})
p1.then((res)=>{
console.log('res',res);
},(err) => {
console.log('err',err);
})
当我们传入一个Promise的时候,能够发现什么事情都没有发生,而且虽然我们调用了resolve方法,但是p1的状态依旧是pengding状态。
const newPro= new Promise((resolve,reject) => {
resolve();
// reject();
});
const p1 = new Promise((resolve,reject) => {
console.log('----');
resolve(newPro);
console.log('e----');
})
p1.then((res)=>{
console.log('res',res);
},(err) => {
console.log('err',err);
})
当我们改变传入的Promise状态时候,当前的Promise也会随着改变自身的状态。
4 传入一个对象,并且这个对象有then方法。那么就会执行该对象的then方法,并且状态是由这个then方法所决定的。
const p1 = new Promise((resolve,reject) => {
const obj = {
// then会接收两个参数:resolve,reject
then:function (resolve,reject) {
// 在这里决定promise的状态,和传递的值
resolve('nnn');
}
}
resolve(obj);
})
p1.then((res)=>{
console.log('res',res);//res => nnn
},(err) => {
console.log('err',err);
})
就像上面所写的一样,如果对象里面有then方法的时候,就需要换一个角度去考虑了,而且该then方法会自动执行,但是单纯的在对象里面写then方法是没有任何效果的,也不会自动执行。这是promise所造成的效果。
then方法
then方法是Promise对象上的一个方法:它其实是放在Promise的原型上的,就比如说是Promise.prototype.then。
then方法接收两个回调函数:
fulfilled的回调函数:当状态变成fulfilled时会回调的函数。reject的回调函数:当状态变成reject时会回调的函数。
const p1 = new Promise((resolve,reject) => {
})
p1.then((res) => {
console.log(res);
},(err) => {
console.log(err);
})
// 相当于
p1.then(res => {
console.log(res);
}).catch((err) => {
console.log(err);
})
then里面的两个回调函数,与then和catch的回调函数是一样的。
then方法的多次调用
then方法是可以进行多次调用的。
const p1 = new Promise((resolve,reject) => {
resolve('你好');
});
p1.then((res) => {
console.log("res1",res);
});
p1.then((res) => {
console.log("res2",res);
});
p1.then((res) => {
console.log("res3",res);
});
就如同上面一样的去调用,但是对于我们来说,三次调用所接收到的值都是一样的。
then方法的返回值
const p1 = new Promise((resolve,reject) => {
resolve('你好');
});
p1.then((res) => {
console.log("res1",res);
return 123
});
// promise链式调用时候,第二个then方法,就是看第一个then方法的返回值
所谓的then方法的返回值,是指的then方法里面回调函数的返回值。
1 当返回值是一个普通类型(数字/字符串/普通对象/undefined)的时候。
p1.then((res) => {
return '123'
});
// 相当于,
p1.then((res) => {
return new Promise((resolve,reject) => {
resolve('aaaa');
})
});
当返回的是一个普通值的时候,那么这个普通的值会被作为一个新的Promise的resolve的值。然后作为then方法的返回值进行一个返回。
then方法本身也是有返回值的,它的返回值是Promise。
p1.then((res) => {
return '123'
}).then((res) => {
console.log(res); // '123'
})
// 多一点,链式调用
p1.then((res) => {
return '123'
}).then((res) => {
console.log(res); // '123';
return 'bbbb'
}).then((res) => {
console.log(res); // bbb
})
所以说我们可以继续进行then方法的调用,而这里的第二个then方法并不是对p1的调用,而是p1.then返回值的调用。
对于一个函数来说,如果没有返回值,那么返回的就是undefined了。
2 返回值是Promise类型。
const p1 = new Promise((resolve,reject) => {
resolve('aaa')
});
p1.then((res) => {
console.log("res1",res);
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve('bbb')
},3000);
})
}).then((res) => {
console.log(res);
})
//两者的比较
p1.then((res) => {
return '123';
// 相当于
return new Promise((resolve,reject) => {
resolve('123');
})
})
p1.then((res) => {
return new Promise((resolve,reject) => {
resolve(new Promise((resolvee,reject) => {
resolve()
}));
})
})
对于普通的值来说,resolve的是某个字符串或者数字,而对于Promise来说这里resolve的就是这个Promise,那么外面的resolve的值,将由里面的Promise的状态来决定,只有当里面的Promise在调用resolve的时候,外面的Promise才会真正的调用resolve方法。
3 如果返回的是一个对象,并且改对象实现了 thenable。
const p1 = new Promise((resolve,reject) => {
resolve('aaa')
});
p1.then((res) => {
return {
then:function (resolve,reject) {
resolve('bbbb');
}
}
}).then((res) => {
console.log(res); // bbbb
})
当对象里面有then方法的时候,then所对应的函数,会接收resolve和reject两种状态,然后通过调用其中的某一个方法,来改变Promise的状态,并且将值给传递出去。
catch方法
const p1 = new Promise((resolve,reject) => {
reject('err message');
})
// 简单的使用
p1.catch(err => {
console.log(err); //err message
})
//使用 throw Error
const p1 = new Promise((resolve,reject) => {
// reject('err message');
throw new Error('错误信息');
})
p1.catch(err => {
console.log(err); // 错误信息
console.log('eeeeeeee');// eeeeeeee
})
当我们调用catch方法的时候,我们就会接收到这个回调,同样当我们使用throw Error的时候,catch同样也能够捕获到错误的信息。
但是不符合Promise a+规范。因为规范里面错误捕获要写在then的第二个回调里面,在ES6中为了更加的方便处理,所以写在了catch里面,效果是完全一样的。
p1.then(res => {
return 111
}).catch(err => {
})
当我们这样去捕获异常的时候,前面说到then会返回一个新的promise对象,那么我们到底捕获的是原来的promise,还是新的promise呢?其实在内部实现的时候,它是捕获原来的promise,而不是then出来这个新的promise。
所以在promise a+规范里面是没有catch的,而对于catch来说,优先捕获原本promise的异常,如果没有捕获到的话,那么就会捕获then返回的这个promise的异常。
const p1 = new Promise((resolve,reject) => {
reject('err message');
})
p1.then(res => {
throw new Error('错误');
}).catch(err => {
console.log(err);//err message
})
那么当catch捕获到原来promise的异常,那么then里面的异常就会捕获不到了。
p1.then(res => {
console.log(res);
}).catch(err => {
console.log(err);//err message
return 'catch1'
}).then((res) => {
console.log(res);//catch1
})
当catch后面继续加一个then的时候。catch里面return出来的值,就是then所接收的值了。
finally方法
finally是在ES9(2018)中新增的新增的一个特性:表示无论promise对象变成fulfilled还是reject状态,最终都会被执行的代码。
finally方法是不接收参数的,因为无论前面是fulfilled还是reject状态,它都会执行。
const p1 = new Promise((resolve,reject) => {
resolve('1111')
});
p1.then((res) => {
console.log(res);// 111
}).catch((err) => {
console.log(err);
}).finally(() => {
console.log('我的世界如此奇妙'); // 我的世界如此奇妙
})
// 当调用 reject 的时候
const p1 = new Promise((resolve,reject) => {
reject('err message');
});
p1.then((res) => {
console.log(res);
}).catch((err) => {
console.log('1',err); // 1 err message
}).finally(() => {
console.log('我的世界如此奇妙'); // 我的世界如此奇妙
})
简单来说就是放在某个Promise的最后,不论Promise的状态变成什么,这个回调里面的代码都会被执行。
resolve方法
前面所学习的then、catch、finally方法属于Promise实例方法,都是放在prototype上面的,而现在所说的都是promise的类方法。
Promise.resolve("hello world");
// 相当于 _ 可以当做占位符号
new Promise((_,reject) => {
resolve("hello world")
})
const obj1 = {
name1:'cs',
age:18
};
// 现在我们是有一个对象,要将他转成promise对象
const p1 = Promise.resolve(obj1);
p1.then(res => {
console.log(res);
})
有时候我们已经有一个现成的内容了,希望将其转成Promise来使用,这个时候我们可以使用 Promise.resolve 方 法来完成。Promise.resolve的用法相当于new Promise,并且执行resolve操作。
reject方法
reject方法和resolve方法是差不多的。
const message = 'err message236';
const p1 = Promise.reject(message);
p1.then(res => {
console.log(res);
}).catch(err => {
console.log(err);
})
Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的
all方法
作用:将多个Promise包裹在一起,形成一个新的Promise。
const p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('1');
},1000);
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('2');
},2000);
})
const p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('3');
},3000);
})
// all里面也可以传递非promise的值,会自动转化为promise,Promise.resolve('aaa')
Promise.all([p1,p2,p3,'aaa']).then(res => {
console.log(res); // [ '1', '2', '3', 'aaa' ]
})
新Promise的状态,由所包裹的所有Promise共同决定。当所有的Promise状态变成fulfilled状态时,新Promise的状态会变成fulfilled,并且将所有的Promise的返回值组成一个数组。
const p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('1');
},1000);
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('2');
},2000);
})
const p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('3');
},3000);
})
Promise.all([p1,p2,p3,'aaa']).then(res => {
console.log(res);
}).catch(err => {
console.log(err);// 2; 2秒的时候出现reject的结果,等待5秒后,所有代码执行完成。
})
当有一个Promise的状态为reject的时候,新Promise的状态也为reject,同时会将第一个reject的返回值作为参数。
allSettled方法
all方法有一个缺陷:当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
const p1 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('1');
},1000);
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('2');
},2000);
})
const p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('3');
},5000);
})
Promise.allSettled([p1,p2,p3,'aaa']).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // 不会有任何的值
})
// res 的值如下所示
[
{ status: 'fulfilled', value: '1' },
{ status: 'rejected', reason: '2' },
{ status: 'fulfilled', value: '3' },
{ status: 'fulfilled', value: 'aaa' }
]
在ES11(ES2020)中,添加了新的API Promise.allSettled:该方法会在所有的Promise都有结果,无论是fulfilled,还是reject时,才会有最终的状态;并且这个Promise的结果一定是fulfilled的;
race方法
如果有一个Promise有了结果,我们就希望决定最终新Promise的状态,那么可以使用race方法:race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
const p1 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('1');
},1000);
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('2');
},2000);
})
const p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('3');
},5000);
})
// 只要有一个 promise 的状态变成 resolve或者reject ,那么就会结束
Promise.race([p1,p2,p3]).then(res => {
console.log('r',res);
}).catch(err => {
console.log('e',err);
})
根据第一个来决定,Promise.race所返回的Promise的状态。
any方法
any方法是ES12中新增的方法,和race方法是类似的:any方法会等到一个fulfilled状态,才会决定新Promise的状态;如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;
const p1 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('1');
},1000);
})
const p2 = new Promise((resolve,reject) => {
setTimeout(() => {
reject('2');
},200);
})
const p3 = new Promise((resolve,reject) => {
setTimeout(() => {
resolve('3');
},5000);
})
// any 方法
Promise.any([p1,p2,p3]).then((result) => {
console.log(result);
}).catch((err) => {
console.log(err);
});
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。本质上,这个方法和Promise.all()是相反的。
promise
promise A+规范网站:promisesaplus.com/。
// 定义Promise的状态
const STATUS_PENDING = 'pending';
const STATUS_RESOLVE = 'resolve';
const STATUS_REJECT = 'reject';
class CSPromise{
constructor(executor){
this.status = STATUS_PENDING;
const resolve = (value=undefined) => {
// 只有当状态为 pending 的时候,才能够改变状态
if (this.status === 'pending') {
// queueMicrotask 将一个任务加入到微任务队列里面
this.status = STATUS_RESOLVE;
queueMicrotask(() => {
//改变为resolve状态
// 然后执行代码
console.log('resolve被调用',value);
// 然后要执行的是 then 传递进来的回调函数
this.onfulfilled(value);
})
}
};
const reject = (reason=undefined) => {
this.status = STATUS_REJECT;
if (this.status === 'pending') {
queueMicrotask(() => {
// 改变为 reject 状态
// 然后执行代码
console.log('reject被调用',reason);
// 然后要执行的是 then 传递进来的回调函数
this.onrejected(reason);
})
}
};
executor(resolve,reject);
}
// 成功和失败
then(onfulfilled,onrejected){
this.onfulfilled = onfulfilled;
this.onrejected = onrejected;
}
}
const p1 = new CSPromise((resolve,reject) => {
// 1 这里面的东西会直接被调用的。
console.log('直接开始');
// 2 调用resolve或者reject
// resolve('xxx');
// reject()
// 3 调用 resolve 或者 reject的时候,传递参数
// resolve('123');//123
// resolve(); // undefined
// reject() // undefined
// reject('456') // 456
resolve('123');
reject('456')
})
// 2 这里涉及到 当调用 resolve 或者 reject在调用之后,不能调用另外一个,所以就需要 状态来进行管理
// then 回调的实现
p1.then((res) => {
console.log('res',res);
})
Promise的简单实现,其中很多的东西都没有完善,比如说多次的调用、链式的调用等等。