出现背景
CoderWHY:新技术的出现,都是为了解决原有的某个痛点
以前都是通过回调函数的方式进行处理
异步编程
同步:为了得到这个结果,必须要计算机等待,等待期间不能执行其他操作
let x =1
console.log(x+=1)
异步:计算这个结果的同时可以执行其他操作,多个命令同时执行
setTimeout(console.log(1), 1000);// 1秒后运行console.log(1)
setTimeout(console.log(2), 500);// 0.5秒后运行console.log(1)
// 先输出2,再输出1
// 原因在于setTimeout 是异步的,并不是执行完第一条setTimeout后,再执行第二条,是同时执行的。
以前处理异步函数(了解)
1、回调函数
就是异步操作执行完后,执行一个函数告诉我们执行完了
function double(value, callback) { // 这里callback就是回调函数,什么名字都可以
setTimeout(() => callback(value * 2), 1000);
}
double(3, (x) => console.log(`I was given: ${x}`));
// I was given: 6(大约 1000 毫秒之后)
2、失败处理
成功回调和失败回调
function double(value, success, failure) {
setTimeout(() => {
try {
if (typeof value !== 'number') {
throw 'Must provide number as first argument';
}
// 成功时执行
success(2 * value);
} catch (e) {
// 失败时执行
failure(e);
}
}, 1000);
}
// 定义函数执行成功时,回调函数
const successCallback = (x) => console.log(`Success: ${x}`);
// 定义函数执行失败时,回调函数
const failureCallback = (e) => console.log(`Failure: ${e}`);
double(3, successCallback, failureCallback);
double('b', successCallback, failureCallback);
// Success: 6(大约 1000 毫秒之后)
// Failure: Must provide number as first argument(大约 1000 毫秒之后)
3、嵌套回调
随着代码越来越复杂,回调策略是不具有扩展性的。“回调地狱”这个称呼可谓名至实归。
嵌套回调的代码维护起来就是噩梦
===============
Promise(期约)
是什么?
应用场景
一般情况下是有异步操作时,使用 Promse对这个异步操作进行封装
基本使用
let a = new Promise((resole,reject) =>{ // 通常命名为resole解决,reject拒绝
// 这里的函数会立刻执行,
// 如果在函数里面执行了resole(),后面就会执行.then 方法,不执行.catch 方法
// 如果在函数里面执行了reject(),后面就会跳过.then 方法,直接执行.catch 方法
// 例子这里放网络请求的代码
let res = request() // 比如这是个例子
if(res == true){ // 这里验证只是举个例子,实际要看返回的结果来作判断
//请求成功的话,请求的结果通过resole函数传到then里面
resole("abc")
}else{
//失败的话,把失败信息传到reject函数里面
reject("fail")
}
}).then(data =>{
//这里可以处理resole传过来的数据
console.log(data) // 这里例子就是上面resole("abc")里面的"abc"
}).catch(err =>{
//这里可以获得reject传过来的错误信息,然后处理
console.log(err) // 这里例子就是上面reject("fail")里面的"fail"
})
Promise 的状态
1、待定(pending)
最初始状态,如果 Promise(()=>{ })里面的函数体,没有调用 resole( ) 完成,或者没有调用reject( ) 失败,则会一直保持 待定状态
只要出了结果,过程是不可逆的,无法重复修改结果。
2、兑现(fulfilled)
有时候也称为“解决”,resolved,表示已经成功完成
3、拒绝(rejected)
表示没有成功完成,此时会提示一定要有个catch方法捕获这个失败的信息
比如,假设期约要向服务器发送一个 HTTP 请求。
请求返回 200~299 范围内的状态码就足以让期约的状态变为“兑现”。
类似地,如果请求返回的状态码不在 200~299 这个范围内,那么就会把期约状态切换为“拒绝”。
===============
resolve 成功的处理
then 方法
说明
对象的方法:首先要new 一个对象,然后执行这个对象.方法()
方法参数
多次调用
(多次调用,共用同一个结果res)
返回值
1、普通值
如果返回值是吗,基本数据类型、对象、数组、undefined(没有return返回)等,会当做新的Promise的resole的值
相当于
2、new Promise
如果返回一个new Promise,则后面的状态(.then .catch),由这个返回的Promise里面的状态决定
3、对象且实现then方法
还是会包一层new Promise 然后返回
相当于
链式调用
原理就是内部会包一层 new Promise 然后返回,下面的就会接收上面的返回值,进行调用(见上面 返回值)
catch 方法(错误处理)
说明
对象的方法:首先要new 一个对象,然后执行这个对象.方法()
捕获错误
除了Promise里面调用reject方法会执行catch,抛出错误(throw)也会触发catch
(这里相当于包装了一层new Promise 然后 return,详见then方法里面的返回值章节)
(then( ) 的第二个参数就是catch( )方法)
这一大段都是 new Error 这个内置的错误对象表示的信息
多次调用
和then一样,可以同一个对象多次调用,这样会以同一个结果来执行多个catch方法
链式调用
参考 then 方法里面的链式调用,只要在链式调用的过程中,出现了 reject 或者 出现了异常new Error 或者 代码报错,
则会跳过下面所有的.then 方法,直接执行.catch( ) 方法(或者某个then() 方法的第二个参数,也是catch方法)
返回值
catch 也有返回值,也是相当于包装了一层 new Promise,然后return出去,因此还可以在后面接then 和 catch
finally 最终执行(ES9)
===============
Promise 的类方法
通过 Promise . 类方法( ) 的方式调用
resolve( ) 直接成功
直接使用 Promise 这个类的方法,可以简化 Promise 的编写。
它的参数和 new Promise 里面resolve的是一样的。见上《resolve 成功的处理》。
reject( ) 直接失败
直接使用 Promise 这个类的方法,可以简化 Promise 的编写。
all( ) 等到一起完成
(多个Promise)
会等到 p1、p2、p3都完成(只要不是待定pending状态,就是成功或失败)后,再执行后面then 或 catch的内容
类似 与&& 操作,所有都成功,返回的就是成功;只要有一个失败,就会立刻返回的就是失败,其他没完成的就不执行了。
(成功的话,会把所有返回的结果,放在数组里面)
(失败的话,返回的结果只有那个失败的结果)
allSettled( ) 等到一起完成+(ES11)
(最终返回的是一个数组,里面按数组的顺序陈列结果和传递的值)
race( ) 第一个完成的
谁先完成就用谁的结果,这个结果是成功的,就调用then;这个结果是失败的,就调用catch
any( ) 任意成功的 (ES12)
然后执行catch,把这个错误传入catch;
你还可以通过err.errors 这个属性获取上面所有Promise 返回的错误值
===============
其他特性
1、比常规代码推后执行*
let p1 = Promise.resolve();
p1.then(() => console.log('5));
console.log('1');
let p2 = Promise.reject();
p2.then(null, () => console.log('6'));
console.log('2');
let p3 = Promise.reject();
p3.catch(() => console.log('7'));
console.log('3');
let p4 = Promise.resolve();
p4.finally(() => console.log('8'));
console.log('4');
// 输出为
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
Promise 的代码,会比一般常规的代码,退后执行(如上例子),是因为 JavaScript 的事件循环机制。
1、首先,代码由上往下,一条一条执行,遇到一般的代码,比如创建变量、执行普通函数等,都会在左边的 内存里面的 堆/栈 结构里面执行。
2、遇到Promise、setTimeout等,就会把这些代码放进下方的回调队列里面排队,等一般代码执行完后,再执行这里队列的。
因此如上例子,才会出现,先输出1234(一般代码,堆/栈里面执行),再输出5678(进队列里面,后面再执行)
2、执行顺序
如果给期约添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行
let p1 = Promise.resolve();
let p2 = Promise.reject();
p1.then(() => setTimeout(console.log, 0, 1));
p1.then(() => setTimeout(console.log, 0, 2));
// 1
// 2
p2.then(null, () => setTimeout(console.log, 0, 3));
p2.then(null, () => setTimeout(console.log, 0, 4));
// 3
// 4
p2.catch(() => setTimeout(console.log, 0, 5));
p2.catch(() => setTimeout(console.log, 0, 6));
// 5
// 6
p1.finally(() => setTimeout(console.log, 0, 7));
p1.finally(() => setTimeout(console.log, 0, 8));
// 7
// 8
3、取消、中断
ES6 期约被认为是“激进的”:只要期约的逻辑开始执行,就没有办法阻止它执行到完成。
但是通过定 Promise.race 的方式,可以实现取消,因为race会返回第一个完成的,其他都不等了
// 1、定义一个正常执行的promise对象,10秒后返回成功
let pRun = new Promise((resolve)=>{
setTimeout(()=>{
resolve('成功')
},10000)
})
// 2、定义一个竞速方法
function promiseRace(p){
let obj = {};
// 3、内部定一个新的promise,用来终止执行
let pCancel = new Promise(function(resolve, reject){
obj.abort = reject;
});
obj.promise = Promise.race([p, pCancel]);
return obj;
}
// 3、调用竞速方法,把要正常执行的方法传入
let obj1 = promiseRace(pRun)
// 4、打印最终结果
obj1.promise.then(res=>{console.log(res)})
// 如果中途要取消,调用取消的方法
obj1.abort('取消执行')
4、超时失败
设定一个时间,超过时间还没完成就返回拒绝。
因为期约的状态只能改变一次,所以这里的超时拒绝逻辑中可以放心地设置让期约处于待定状态的最长时间。
如果执行器中的代码在超时之前已经解决或拒绝,那么超时回调再尝试拒绝也会静默失败。
let p = new Promise((resolve, reject) => {
// 先设置超时时间,如 10 秒后调用 reject()
setTimeout(reject, 10000);
// 在执行函数的逻辑
// ...
});
==================
TypeScripts 中使用
Promise 的类型,是这里的泛型规定的,你这里是什么类型,resolve 和 reject函数里面的就是什么类型。