Promise
Promise:ES6新增的内置类(构造函数),用来规划异步编程代码,解决回调地狱问题
let p1 = new Promise([executor])
+ [executor]必须是一个函数,而且new Promise的时候会立即把其执行 “同步”
+ p1是其创建出来的实例
私有属性
+ [[PromiseState]]:"pending"、"fulfilled"、"rejected" 实例的状态
+ [[PromiseResult]]:undefined 实例的值「成功的结果或失败的原因」
公共方法:Promise.prototype
+ then
+ catch
+ finally
+ ...
+ p1.then([onfulfilled],[onrejected])
+ [onfulfilled]/[onrejected]都是函数
+ 实例状态是成功fulfilled的时候,会把[onfulfilled]执行,并且把实例的值作为成功的结果传递给他
+ 实例状态是失败rejected的时候,会把[onrejected]执行,把实例值作为失败原因传递给他
回调地狱
以jQuery的ajax请求为例,在回调函数里面嵌套回调函数称为回调地狱
$.get('./data.json', function cb1(data) {
$.get('./data2.json', function cb2(data2) {
$.get('./data3.json', function cb3(data3) {
$.get('./data4.json', function cb4(data4) {
console.log(data, data2, data3, data4)
})
})
})
})
如何修改实例的状态和值
//基于这种方式创建实例
let p=new Promise((resolve,reject)=>{
...
})
+ 如果executor函数执行报错,则把实例的状态修改为rejected,值是报错原因「不会抛异常」
+ resolve/reject也是函数
resolve('OK') -> 把实例p的状态修改为fulfilled,值(成功结果)是'OK'
reject('NO') -> 把实例p的状态修改为rejected,值(失败原因)是'NO'
+ 一但状态被修改为fulfilled或rejected,后期就不会再更改状态值了
Promise的公共方法
then()
每一次执行THEN方法,都会返回一个“全新的promise实例”
let p2 = p1.then(onfulfilled,onrejected);
不论是onfulfilled还是onrejected执行(由p1状态来决定),方法的执行决定了p2的状态和值
+ 首先看方法执行是否报错,如果报错了,则p2是失败态(rejected),值是报错原因
+ 如果执行不报错,再看方法的返回值
+ 如果返回的是“新的promise实例 -> @NP”,则@NP的状态和值直接决定了p2的状态和值
+ 如果返回的不是新实例,则p2状态是成功(fulfilled),值是函数的返回值
执行Promise.resolve/reject/all/any/race...等静态私有方法,也会创建新的promise实例
+ Promise.resolve(10) 创建一个状态是成功fulfilled,值是10的实例
+ Promise.reject(0) 创建一个状态是失败rejected,值是0的实例
let p1 = new Promise(function (res, rej) {
res(888, 1, 2, 3, 4, 5, 6);//将实例状态变为成功态
// res执行的时候可以让then的第一个回调函数执行
// then的第一个回调函数 只有当实例的状态由pending变成fulfilled的时候才会触发
// res 执行的时候的第一个“参数”会传给then的“第一个回调函数”,res的是他参数没用
})
p1.then(function then的回调函数1(data) {
console.log(1111, data);//实例为成功态时执行这个函数 值是888
}, function then的回调函数2(err) {
console.log('err', err);//实例为失败态执行then的第二个函数 值是rej执行传递的第一个参数
})
catch()
THEN链的顺延/穿透机制
在项目中:then中一般只传递onfulfilled「成功干什么」,最后加一个catch;这样不论中间哪个环节创建了失败的实例,都会穿透至最后一个catch;catch不加,出现失败的实例,控制台报“红”,但是不影响其他代码执行!!
.then(onfulfilled,onrejected),两个方法可以传可以不传,如果不传,则顺延至下一个THEN中,相同状态要执行的方法
中去处理!!
+ 原理:我们不设置对应的方法,PROMISE内部会默认加一个对应的方法,可以让其实现状态的顺延/穿透
p.catch(onrejected) 等价于 p.then(null,onrejected)
let p1 = new Promis((res,rej){
rej(error);
})
//then链 连续的then:then执行会返回新的Promise实例(如果代码执行没有返回Promise实例的话) 所以可以调用then
p1.then((data)=>{
console.log(data,'第一个成功态');
})
.then((value)=>{
console.log(value,'第二个成功态');
})
.then((value1)=>{
console.log(value1,'第三个成功态')
})
.catch((error)=>{
console.log(error,'失败态');//此处执行catch,then链中都没有写第二个函数,所以会顺延至catch
})
finally()
Promise.resolve(10)
.then(value => {
console.log('成功', value);
return value * 10;
})
.then(value => {
console.log('成功', value);
return Promise.reject(value * 10);
})
.then(value => {
console.log('成功', value);
return value * 10;
})
.catch(reason => {
console.log('失败', reason);
})
.finally(()=>{
console.log('最后')
//不论成功还是失败,最后都有要执行finally中的方法「一般很少使用」
});
关于Promise.all/any/race三个方法
let p = Promise.all/any/race([promises]);
+ promises是包含零到多个promise实例的集合,一般是数组!如果集合中的某一项不是promise实例,则默认变为状态为成功,值是本身的promise实例!!
+ all:集合中的“每个实例都成功”,最后结果p才成功,值是按照集合顺序,依次存储每个实例成功结果的数组;其中只要有一个实例失败,则p就是失败的,值是本次失败的原因,后面的操作不再处理!!
+ any:只要有一个成功,最后p就是成功的,值是本次成功的结果;都失败,最后p才是失败!{兼容性不好}
+ race:集合中谁最先知道结果,则以谁的为主!
AJAX的串行和并行:在平时的项目中发送ajax请求都是"异步编程"
并行:多个请求同时发送,谁先回来就先处理谁,经常用于多个请求之间没有依赖的时候偶尔我们需要监测,多个请求都成功,再去做什么事==>Promise.all 串行:多个请求之间存在依赖,无法同时发送请求,上一个请求发送成功,我们才能发送下一个请求(往往是下一个请求需要用到这一个请求来的结果,才需要这么做)
AJAX并行
$.ajax({
url: '/api1',
success(result) {
console.log(`第一个请求成功:`, result);
}
});
$.ajax({
url: '/api2',
success(result) {
console.log(`第二个请求成功:`, result);
}
});
$.ajax({
url: '/api3',
success(result) {
console.log(`第三个请求成功:`, result);
}
});
JQ中的AJAX串行 -> 回调地狱
$.ajax({
url: '/api1',
success(result) {
console.log(`第一个请求成功:`, result);
$.ajax({
url: '/api2',
success(result) {
console.log(`第二个请求成功:`, result);
$.ajax({
url: '/api3',
success(result) {
console.log(`第三个请求成功:`, result);
}
});
}
});
}
});
基于Promise解决以上回调地狱的问题
axios.get('/api1')
.then(value => {
console.log(`第一个请求成功:`, value);
return axios.get('/api2');
})
.then(value => {
console.log(`第二个请求成功:`, value);
return axios.get('/api3');
})
.then(value => {
console.log(`第三个请求成功:`, value);
});
//上一个then执行完后返回一个新实例,才会执行下一个then
async/await
async/await:是Promise+Generato的"语法糖"
async是对函数的修饰 ==>async function xxx(){....}
+ 让函数的返回结果自动变为一个Promise'实例
- 如果函数执行报错,就返回一个rejected状态的实例 值是报错原因
- 如果函数没有报错,再看返回值
- 返回值是新的Promise实例,则以函数返回的实例为准
- 其他情况则返回一个fulfilled状态的实例 值是函数的返回值
+ 可以在函数中使用await
await可以监听Promise实例的状态,从而决定去做什么事 -->let xxx= await [Promise实例];
+ 必须出现在一个函数中,并且是经过async修饰过的函数
+ await后面需要跟一个Promise实例[如果不是Promise实例,浏览器会自动将他变为Promise实例]
await 14; => await Promise.resolve(14);
+ await会等待后面的实例状态为“成功”时才会把“当前上下文中”await下面的代码执行 xxx就是实例为成功返回的结果
+ 如果await后面的实例状态为失败,则下面代码不会执行(控制台会“爆红”,但是不影响代码执行)
+ 我们基于try/catch实现对await后面的实例为失败态的处理,避免出现爆红
模拟从服务器获取数据
// 模拟从服务器获取数据
const query = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve({ code: 0, data: [10, 20, 30] });
reject('请求失败');
}, 2000);
});
};
(async function() {
try {
let result = await query();
//在不知道请求结果之前,当前上下文await下面的代码是不会执行;只有后面实例状态是成功,下面代码才执行;
//如果实例状态是失败,下面代码也不执行「因为我们没有做失败情况的处理,所以控制台抛红」
console.log('请求成功:', result);
} catch (reason) {
console.log('请求失败:', reason); // 实例状态是失败
}
query()
.then(result => {
console.log('请求成功:', result);
})
.catch(reason => {
console.log('请求失败:', reason);//失败状态穿透至catch
});
})();
async/await练习
const sleep = (interval = 1000) => {
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, interval);
});
};
const handler = async () => {
await sleep(2000);
console.log('第一个定时器触发执行');
await sleep(1000);
console.log('第二个定时器触发执行');
await sleep(3000);
console.log('第三个定时器触发执行');
};
handler().then(() => {
console.log('当函数内部所有await都处理完,而且对应的实例都是fulfilled,才会让handler执行的返回值是成功的promise实例!');
}).catch(() => {
console.log('代码执行报错,或者其中某个await后面的实例是失败的,则直接让handler返回值也是失败的');
});
sleep(2000)
.then(() => {
console.log('第一个定时器触发执行');
return sleep(1000);
})
.then(() => {
console.log('第二个定时器触发执行');
return sleep(3000);
})
.then(() => {
console.log('第三个定时器触发执行');
});
Promise & async/await 中的异步和同步机制
异步微任务 & 异步宏任务
浏览器是多线程,但是它只分配一个“JS引擎线程”用来渲染和解析JS代码,所以JS是单线程的!!
+ JS中大部分代码都是“同步编程”,例如:循环...
+ 千万不要写死循环,一但遇到死循环,则JS引擎线程会一直被占用,其它事情都做不了了
+ 遇到程序抛出异常,后面的代码也不会再执行了
+ 我们可以基于 try/catch 进行异常捕获,这样不会影响后续代码的执行
+ ...
JS中也存在“异步编程”:依托于浏览器多线程,再基于EventLoop事件循环机制处理的
+ 异步宏任务 macrotask
+ 定时器 setTimeout/setInterval
+ 事件绑定
+ ajax/fetch
+ ...
+ 异步微任务 microtask
+ requestAnimationFrame
+ Promise.then/catch/finally
+ async await
+ queueMicrotask 基于这个方法可以创建一个异步微任务
+ IntersectionObserver
+ ...
异步微任务优先于异步宏任务执行,即使宏任务比微任务先进入EventQueue中等待
案例练习
目前已知实例状态案例
/*
new Promise(resolve => {
// EXECUTOR函数会被立即执行「同步」:这里一般是管理异步编程代码,当异步结束,基于resolve/reject执行,控制实例状态的成功或者失败...
console.log(1); 先打印1
});
console.log(2); 再打印2
*/
let p1 = new Promise(resolve => {
/!*
resolve/reject执行:
+ 立即修改实例的状态和值「同步」
*!/
resolve(100);
});
console.log(p1); // => 实例状态为fulfilled & 值为100
/* p1.then(onfulfilled,onrejected)
+ 如果此时已知p1实例的状态(不是pending),会根据状态去执行onfulfilled/onrejected;
+ 但并不会立即执行,而是创建一个“异步的微任务”
+ 进入到WebAPI中去监听,但是此时已经知道状态是成功的,则直接在挪至EventQueue中排队
+ 当同步代码执行完,主线程空闲下来了,再去EventQueue中查找 */
p1.then(value => {
console.log('成功:', value);
}, reason => {
console.log('失败:', reason);
});
console.log(111);
目前位未知实例状态案例
let p1 = new Promise(resolve => {
setTimeout(() => {
/*
立即修改实例的状态和值「同步」
通知之前在集合中存储的方法执行「异步微任务」
+ 把存储的方法扔到WebAPI中去监听,但是发现实例状态已经是成功了,则挪至到EventQueue中排队等着
+ 把此上下文中剩下的代码执行完,再去获取这个异步任务执行
*/
resolve(100);
console.log(222, p1);
}, 2000);
});
console.log(p1); // 目前实例状态为pending & 值为undefined
/*
执行THEN的时候,此时不知道p1实例的状态
+ 把 onfulfilled & onrejected 存储到PROMISE内部的集合中
*/
p1.then(value => {
console.log('成功:', value);
});
console.log(111);
遇到await案例
/*
遇到await
+ 把await后面的东西进行处理「同步」,获取一个promise实例
+ 然后把当前上下文中,await下面的代码,设置为“异步的微任务”
+ 进入WebAPI监听,当await后面的实例是成功的,再挪至EventQueue中排队等着
*/
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');微1
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {宏1
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {微2
console.log('promise2');
});
console.log('script end');
打印先后顺序为:1.script start 2.async1 start 3.async2 4.promise1 5.script end 6.async1 end
7.promise2 8.setTimeout