一、回调函数
回调函数本身是同步代码
通常在编写JavaScript代码时,使用的回调嵌套的形式大多是异步函数,所以会导致部分开发者认为凡是回调形式的函数都是异步流程。其实并不是这样的,真正的解释是:JavaScript中的回调函数结构,默认是同步的结构,由于JavaScript单线程异步模型的规则,如果想要编写异步的代码,必须使用回调嵌套的形式才能实现,所以回调函数结构不一定是异步代码,但是异步代码一定是回调函数结构。
采用默认的上下结构永远拿不到异步回调中的结果,这也是为什么异步流程都是回调函数的原因 。
二、Promise介绍
注意:Promise是同步方法,promise.then是异步方法
console.log('--开始----');
const p = new Promise(function(resolve, reject) {
console.log('调用Promise');
// resolve可以触发then中的回调执行
resolve('执行了resolve');
// reject('异常了');
});
p.then(res => {
console.log('then 执行', res);
})
.catch(err => {
console.log('catch 执行', err);
})
.finally(() => {
console.log('finally 执行');
});
console.log('--结束----');
执行结果:
--开始----
调用Promise
--结束----
then 执行 执行了resolve
finally 执行
通过上例,我们可以知道new Promise中的回调函数确实是在同步任务中执行的,其次是如果这个回调函数内部没有执行resolve或者reject那么p对象后面的回调函数内部都不会有输出,而运行resolve函数之后.then和.finally就会执行,运行了reject之后,catch和finally就会执行。
Promise的组成
每一个Promise对象都包含三部分
[[Prototype]]: 代表Promise的原型对象
[[PromiseState]]: 代表Promise对象当前状态
[[PromiseResult]]: 代表Promise对象的值,分别对应resolve或reject传入的结果
Promise的三种状态:
- pending:初始状态,也叫就绪状态,这是在Promise对象定义初期的状态,这时Promise仅仅做了初始化并注册了他对象上所有的任务。
- fulfilled:已完成,通常代表成功执行了某一任务,当初始化中的resolve执行时,Promise的状态就变更为fulfilled,并且then函数注册的回调函数会开始执行,resolve中传递的参数会进入回调函数作为形参。
- rejected:已拒绝,通常代表执行了一次失败任务,或者流程中断,当调用reject函数时,catch注册的回调函数就会触发,并且reject中传递的内容会变成回调函数的形参。
Promise的链式调用
链式调用的本质就是在调用这些支持链式调用的函数的结尾时,又返回了一个包含他自己的对象或一个新的自己,这些方式都可以实现链式调用。
// 链式调用
function MyPromise() {
return this;
}
MyPromise.prototype.then = function() {
console.log('触发了then');
return this;
};
new MyPromise().then().then();
三、Promise的注意事项
链式调用
// 链式调用的注意事项
let pr = new Promise((resolve, reject) => {
resolve('promise实例');
});
pr.then(res => {
console.log('res'); // undefined
return '123';
})
.then(res => {
console.log('res'); // 123
return new Promise(resolve => {
resolve(456);
});
})
.then(res => {
console.log('res'); // 456
return '直接返回的结果';
})
.then()
.then('字符串')
.then(res => {
console.log('res'); // 直接返回的结果
});
1、只要有then()并且触发了resolve,整个链条就会执行到结尾,这个过程中的第一个回调函数的参数是resolve传入的值
2、后续每个函数都可以使用retrun返回一个结果,如果没有返回结果的话,下一个then中回调函数的参数就是undefined
3、返回结果如果是普通变量,那么这个值就是一个then中回调函数的参数
4、如果返回的是一个Promise对象,那么这个Promise对象resolve的结果会变成下一次then中回调的函数的参数【直接理解为,返回Promise对象时,下一个then就是该对象的then】
5、如果then中传入的不是函数或者未传值 ,Promise链条并不会中断then的链式调用,并且在这之前最后一次的返回结果,会直接进入离它最近的正确的then中的回调函数作为参数
Promise中断
有两种形式可以让then的链条中断,如果中断还会触发一次catch的执行
// 链式调用的中断
let pe = new Promise((resolve, reject) => {
resolve('Promise的值');
});
pe.then(res => {
console.log(res);
})
.then(res => {
// 两种方式中断Promise
// throw '通过throw中断';
return Promise.reject('reject中断');
})
.then(res => {
console.log(res);
}).catch(res => {
console.log('catch执行', res);
});;
四、Promise常见的API
1. Promise.all()
当我们在代码中需要使用异步流程控制时,可以通过Promise.then来实现让异步流程一个接一个的执行,假设有三个接口,并保证三个接口的数据全部返回后,才能渲染页面。如果a耗时1s、b耗时0.8s、c接口耗时1.4s,如果用Promise.then来执行流程控制,如果通过then函数的异步控制,必须等待每个接口调用完毕才能调用下一个,这样总耗时就是 1+0.8+1.4=3.2s
。这种累加显然增加了接口调用的时间消耗,所以Promise提供了一个all方法来解决这个问题:
Promise.all([promise对象,promise对象,...]).then(回调函数)
回调函数的参数是一个数组,按照第一个参数的promise对象的顺序展示每个promise的返回结果。
借助Promise.all来实现,等最慢的接口返回数据后,一起得到所有接口的数据,那么这个耗时将会只按照最慢接口的消耗时间1.4s执行,总共节省了1.8s.
Promilse.all相当于统一处理了多个promise任务,保证处理的这些所有promise对象的状态全部变成为fulfilled之后才会触发all的.then函数来保证将放置在all中的所有任务的结果返回
2.Promise.race()
Promise.race([promise对象,promise对象,...]).then(回调函数)
回调函数的参数是前面数组中最快一个执行完毕的promise的返回值。
Promilse.race()相当于将传入的所有任务进行了一个竞争,他们之间最先将状态变成fulfilled的那一个任务就会直接的触发race的.then函数并且将他的值返回,主要手于多个任务之间竞争时使用
五、Async和Await
async和await相当于使用了自带执行函数的Generator函数,所以async和await逐渐成为主流异步流程控制的终极解决方案。
async handleTest() {
console.log('1');
// this.test();// await方法
this.promisTest(); // 等价promise方法
console.log('2');
// 输出顺序:1,3,2,4
},
// await方法
async test() {
console.log(3);
var a = await 4;
console.log(a);
},
// 此方法是test方法的promsise写法
promisTest() {
new Promise(resolve => {
console.log(3);
resolve(4);
}).then(res => {
console.log(res);
});
}
async函数中有一个最大的特点,就是第一个await会作为分水岭一般的存在,在第一个await的右侧和上面的代码,全部是同步代码区域相当于new Promise的回调,第一个await的左侧和下面的代码,就变成了异步代码区域相当于then的回调。
总结
从回调地狱到Promise的链式调用到Generator函数的分步执行,再到async和await的自动异步代码同不化机制,经历了很长时间。Promise和事件循环系统并不是JavaScript中的高级知识,而是真正的基础知识。