Prommise:承诺者模式;ES6新增的内置类,基于Prommise可以有效管理“异步编程”,避免回调地狱
ajax”串行”:上一个请求成功,才可以发送下一个请求
ajax”并行”:多个请求可以同时发送
需求:三个ajax请求,我们要实现ajax的“串行”【上一个请求成功,才可以发送下一个请求;同理还有一个ajax“并行”:多个请求可以同时发送(偶尔需要等所有并行请求成功后,统一做啥事)】
1.基于ajax的同步操作,实现出ajax“串行”;但是真实项目中是不允许使用ajax同步处理(因为请求没成功之前,所有的其他事情都被阻碍,也无法实现ajax的并行);
/* let data =null;
$.ajax({
url:'./api/data1.json',
async:false,
success(result){
data = result;
}
});
console.log(`第一个请求成功,结果是:${data}`);
$.ajax({
url:'./api/data2.json',
async:false,
success(result){
data = result;
}
});
console.log(`第二个请求成功,结果是:${data}`);
复制代码
- JQ中的ajax是基于回调函数的方式管理的【请求成功,会触发success回调函数执行,result就是本次请求获取的结果;如果想实现ajax串行,需要吧下一个请求发送,放在上一个请求成功的回调函数中处理,如果有多个串行请求,就会一层层的嵌套==>‘回调地狱’【代码看起来很乱、不方便管理】;
- 痛点:在传统方案中,基于回调函数的方式管理异步编程的代码,总是要在异步任务可执行的时候,在他的回调函数中处理一些事情,这样很容易就产生回调地狱!
$.ajax({
url: './api/data1.json',
// async:false,
success(result) {
console.log(`第一个请求成功,结果是:${result}`);
$.ajax({
url: './api/data2.json',
// async:false,
success(result) {
console.log(`第二个请求成功,结果是:${result}`);
$.ajax({
url: './api/data3.json',
success(result) {
console.log(`第三个请求成功,结果是:${result}`);
}
});
}
});
}
});
复制代码
const qurey1= ()=>{
return new Promise(resolve=>{
$.ajax({
url:'./api/data1.json',
success(result){
resolve(result);
}
});
});
};
const qurey2= ()=>{
return new Promise(resolve=>{
$.ajax({
url:'./api/data2.json',
success(result){
resolve(result);
}
});
});
};
const qurey3= ()=>{
return new Promise(resolve=>{
$.ajax({
url:'./api/data3.json',
success(result){
resolve(result);
}
});
});
};
//解决方案Prommise
qurey1().then(result=>{
console.log(`第一个请求成功,结果是:${result}`);
return qurey2();
}).then(result=>{
console.log(`第二个请求成功,结果是:${result}`);
return qurey3();
}).then(result=>{
console.log(`第三个请求成功,结果是:${result}`);
});
//解决方案: async / await「Promise + generator的语法糖」
(async function(){
let result = await qurey1();
console.log(`第一个请求成功,结果是:${result}`);
result = await qurey2();
console.log(`第二个请求成功,结果是:${result}`);
result = await qurey3();
console.log(`第三个请求成功,结果是:${result}`);
})();
复制代码
Primose语法
let p = new Promise([exector]);
- p 是他的实例
- [exector]是一个函数,传递的不是函数就会报错;
- p.proto===Promise.prototype
私有属性:
[[PromiseState]]: “pending” 存放状态的
[[PromiseResult]]: undefined(存储的是成功的结果/失败的原因)
- Promise实例状态:pending准备状态 、 fulfilled/resolved成功状态 、 rejected失败状态
- 最初的状态是pending,后期基于某些操作可以把状态改变为fulfilled/rejected(一旦状态变为成功或者失败就再也不能改变为其他状态),而[[PromiseResult]]: undefined(存储的是成功的结果/失败的原因)
公有属性:
then, catch, finally, Symbol(Symbol. toStringTag): “Promise’
疑惑一:状态改完就什么用?
- 基于then可以存放俩个函数 p.then(onfulfilled,onrejected)
- 当我们把状态修改为fulfilled成功态,则会把onfulfilled这个函数执行,相反,我们把状态修改为rejected失败态,则会把onrejected这个函数执行…
- 并且会把[[PromiseResult]]作为实参值,传递给onfulfilled/onrejected
疑惑二:怎么修改状态
- new Promise时,会立即把传进来的exector函数执行,并且会给exector传递俩实参进来,我们会用俩个形参变量接收
- resolve,reject,并且值是俩个函数
- 当我们执行resolve,Promise实例状态改变为fulfilled成功,传递的值就是成功的结果,赋值给[[PromiseResult]];同理,只要reject执行,Promise实例状态改变为rejected失败,传递的值就是失败的原因,赋值给[[PromiseResult]]5
- 如果exector执行报错,则实例的状态也是rejected失败,失败原因就是错误信息
let p = new Promise(function exector(resolve,reject) {
});
p.then(function onfulfilled(result){
},function onrejected(reason){
});
new Promise((resolve,reject)=>{
//在这里一般管理异步编程的代码
$.ajax({
url:'./api/data1.json',
success(result){
resolve(result);
},error(reason){
reject(reason);
}
});
setTimeout(()=>{
let ran = Math.random();
if(ran>0.5) resolve('ok');
reject('no');
})
}).then(result=>{
console.log('请求成功',result);
},reason=>{
console.log('请求失败',reason);
});
复制代码
promise实例.then的用途
我们可以基于”promise实例.then”存放多个onfulfilled/onrejected方法,状态变为成功或者失败,存放每个对应的方法都会被执行
let p1 = new Promise((resolve, reject) => {
resolve(100);
});
p1.then(result => {
console.log(`成功:${result}`);
}, reason => {
console.log(`失败:${reason}`);
});
p1.then(result => {
console.log(`成功:${result}`);
}, reason => {
console.log(`失败:${reason}`);
});
复制代码
new promise 产生的实例,他的状态是成功还是失败,取决于“(resolve, reject”执行 或者executor执行是否报错)
let p1 = new Promise((resolve, reject) => {
reject(100);
});
复制代码
每一次执行then方法,不仅存放了onfulfilled/onrejected方法,而且还会返回一个“全新的promise实例”
新实例p2的状态和值,由谁来决定?
不论onfulfilled/onrejected执行的是哪一个,我们只看执行是否报错;如果报错则p2的状态是失败(reject),值是报错原因;如果不报错则p2的状态是成功fulfilled,值是函数的返回值!
let p2 = p1.then(result => {
console.log(`成功:${result}`);
return 1000;
}, reason => {
console.log(`失败:${reason}`);
return -1000;
});
let p3 = p2.then(result => {
console.log(`成功:${result}`);
throw new Error('xxxx')
}, reason => {
console.log(`失败:${reason}`);
});
p3.then(result => {
console.log(`成功:${result}`);
}, reason => {
console.log(`失败:${reason}`); //失败:Error: xxxx
});
复制代码
特殊情况:我们之前说不论onfulfilled/onrejected执行,只要不报错,则实例p2的状态就是成功,只要报错就是失败…….但是有个特殊情况:“执行不报错,但是返回的值是一个全新的promise实例,这样返回值的这个promise实例是成功还是失败,直接决定p2是成功还是失败”
Promise中的then链机制:
- 因为每一次.then都会返回一个新的promise实例,所以我们就可以持续 .then下去了
- 而且因为实例的诞生的方式不同,所以判断标准不同
当promise.then(onfulfilled.onrejected)
- 情况一:我此时已经知道promise是成功还是失败状态de
我们应该去执行onfulfilled或者onrejected,但是不是立即执行,它是一个异步的微任务;
首先把执行对应的方法这个事情放在WebAPI中监听,但是因为此时已经知道状态了,对应的方法肯定执行,所以紧接着把他挪至到EventQueue中【任务等待执行队列】等待执行
- 情况二:此时promise还是pending状态
我们把onfulfilled或onrejected先存储起来,只有当后面,我们把实例的状态修改为成功/失败的时候,再取出之前存储的方法,把其执行【而且此时再执行,还是个异步微任务】
还是要经历:WebAPI->EventQueue
then链具有穿透性(顺延):
- 正常情况下.tnen会传递俩个函数onfulfilled/onrejected,但是有些时候我们是不传递其中的某个函数的,例如:.then(null,onrejected) 或则 .then(onfulfilled) ,这种情况,我们要采取顺延策略;找到下一个then中对应的状态的函数执行
- 几个小例题来巩固then链的穿透性:
Promise.reject(0).then(result => {
console.log(`成功:${result}`);
return 1;
}).then(result => {
console.log(`成功:${result}`);
return 2;
}).then(result => {
console.log(`成功:${result}`);
return 3;
}, reason => {
console.log(`失败:${reason}`); //失败:0
});
Promise.resolve(100).then(result => {
console.log(`成功:${result}`); //成功:100
throw '我失败了';
}).then(result => {
console.log(`成功:${result}`);
return 2;
}).then(result => {
console.log(`成功:${result}`);
return 3;
}, reason => {
console.log(`失败:${reason}`); //失败:我失败了
});
Promise.resolve(100).then(result => {
console.log(`成功:${result}`); //成功:100
return 1;
}).then(result => {
console.log(`成功:${result}`); //成功:1
return Promise.reject('NO');
}).then(null, reason => {
console.log(`失败:${reason}`); //失败:NO
});
复制代码
catch
catch的用途
- .catch(onrejected)===>.then(null,onrejected)
- 真实项目中,我们经常:then中只传递onfulfilled,处理状态是成功的事情;在then链的末尾设置一个catch,处理失败的事情(依托于then链的穿透机制,无论最开始还是哪个then中,出现了让状态为失败的情况,都会顺延到最末尾的catch部分进行处理)
JS中的异常捕获
try中执行代码,如果执行不报错,则不走catch,如果执行报错,直接进入到catch中,而且会用一个形参来接收报错的原因,但是不论执行是否报错,都会走最后的finally
try/catch的作用:捕获异常信息,不影响下面的代码执行;还可以收集错误信息,发送给后台做统计
try{
//放异常信息
}catch(err){
console.log(err);
};
复制代码
Promise.all([promise1,promise2,promise3])并行管控
并行中的综合处理:
一起发送多个请求(处理多个异步),但是需要等待所有异步都成功,我们在做一些事
- 执行Promise.all返回一个新的promise实例@p
- 并且传递一个数组,数组中包含N多个其他的promise实例
- 如果数组中的每一个promise实例最后都是成功的,则@p也将会是成功的,它的值也是一个数组,按照顺序依次存储各个promise实例的结果;但凡数组中的某个promise实例是失败的,则@P也是失败的,值是当前这个实例失败的原因!
- 如果数组中的某一项并不是promise实例(只要不是promise实例就会按默认机制来),则浏览器也会默认变成一个状态是成功的,promise实例,值就是当前项本身;
- 返回结果顺序和最开始是一致的,不会考虑谁先成功
异步的“串行”
第一个异步成功后才能发送第二个,第二个成功才能发送第三个….多个异步之间一般是有依赖的
const fn1 = ()=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(1);
},1000);
});
};
const fn2 = ()=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(2);
},2000);
});
};
const fn3 = ()=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(3);
},3000);
});
};
fn1().then(reslut=>{
console.log(`第一个成功${result}`);
return fn2();
}).then(reslut=>{
console.log(`第二个成功${result}`);
return fn3();
}).then(reslut=>{
console.log(`第三个成功${result}`);
}).catch(reason=>{
console.log(`只要其中一个失败,直接穿透(顺延)到这里,其余剩下的请求就不发送了`);
}) */
// 真实项目中,想实现异步的串行,我们一般使用async
(async function(){
let result = await fn1();
console.log(`第一个成功${result}`);
result = await fn2();
console.log(`第二个成功${result}`);
result = await fn3();
console.log(`第三个成功${result}`);
})();
复制代码
async函数+await
- promise状态是失败,如果不用catch(或者onrejected)处理,控制台会抛出异常:Uncaught(in promise)xxx,但是此异常不会阻碍下面代码的执行
async修饰符:修饰一个函数,让函数的返回值成为一个promise实例
- 如果函数自己本身就返回一个promise实例,则以自己返回的为主
- 如果函数本身没有返回promise,则会把返回值变成一个promise实例;状态->成功 值->返回值
- 如果函数执行报错,则返回的实例 状态->失败 值->报错原因
async最主要的作用:如果想在函数中使用await,则当前函数必须基于async修饰
await的作用
await:等待,我们一般在其后面放promise实例,她会等待实例状态为成功,再去执行当前上下文中,await下面的代码,【如果管控的是一个异步编程,其实他是在等待异步成功,在执行下面的代码,类似于异步改为同步效果】;
如果不是promise实例,浏览器默认会把其转换为“状态为成功,值就是这个值”的实例;
const fn1 = ()=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(1);
},1000);
});
};
(async function(){
let result = await fn1();
console.log(result); //下面代码可以执行,说明await后面的promise实例,它的状态已经是成功了,await的返回值就是当前promise实例的值
})();
复制代码
- 如果await后面的promise实例状态为失败的,则下面代码永远不会执行
同步异步执行
同步代码执行,遇到一个异步任务
- 先把其放到webAPI进行监听
- 当前异步任务监听到可以执行,则再把其放到在EventQueue中,排队等待执行
同步任务执行结束,主线程空闲
- 去EventQueue中找可执行的微任务,如果微任务都执行完了,再去找可执行的宏任务【队列:优先级队列&先进先出】
- 取到的任务都放在Stark中交给主线程去执行
这就是事件的循环机制EventLoop
await中的异步:当前上下文,await下面的代码执行是异步微任务
- 情况一:await后面的promise实例我们已经是成功的
先把微任务放置在WebAPI中,但是知道是可以执行的,则直接再挪至到EventQueue中等待
- 情况二:await后面的promise实例还是pending状态
此时我们把微任务放置在WebAPI中监听,等到后期promise实例是成功态后,再把它挪到EventQueue中等待执行即可
练习题【来巩固一下promise和同步异步执行吧】
例题1
console.log(1);
setTimeout(() => {
console.log(2);
});
console.log(3);
let p1 = new Promise(resolve => {
console.log(4);
resolve('A');
console.log(5);
});
console.log(6);
p1.then(result => {
console.log(result);
});
console.log(7);
let p2 = new Promise(resolve => {
setTimeout(() => {
resolve('B');
console.log(10);
});
});
console.log(8);
p2.then(result => {
console.log(result);
});
console.log(9);
复制代码
例题2
let body = document.body;
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(1);
});
console.log(2);
});
body.addEventListener('click', function () {
Promise.resolve().then(() => {
console.log(3);
});
console.log(4);
});
复制代码
例题3
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function () {
console.log('setTimeout');
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1');
resolve();
}).then(function () {
console.log('promise2');
});
console.log('script end');
复制代码
例题4
console.log('start');
let intervalId;
Promise.resolve().then(() => {
console.log('p1');
}).then(() => {
console.log('p2');
});
setTimeout(() => {
Promise.resolve().then(() => {
console.log('p3');
}).then(() => {
console.log('p4');
});
intervalId = setInterval(() => {
console.log('interval');
}, 3000);
console.log('timeout1');
}, 0);
答案:"start" "p1" "p2" "timeout1" "p3" "p4" "interval"
复制代码
例题5
setTimeout(() => {
console.log('a');
});
Promise.resolve().then(() => {
console.log('b');
}).then(() => {
return Promise.resolve('c').then(data => {
setTimeout(() => {
console.log('d')
});
console.log('f');
return data;
});
}).then(data => {
console.log(data);
});
答案:"b" "f" "c" "a" "d"
复制代码
例题6
function func1() {
console.log('func1 start');
return new Promise(resolve => {
resolve('OK');
});
}
function func2() {
console.log('func2 start');
return new Promise(resolve => {
setTimeout(() => {
resolve('OK');
}, 10);
});
}
console.log(1);
setTimeout(async () => {
console.log(2);
await func1();
console.log(3);
}, 20);
for (let i = 0; i < 90000000; i++) {} //循环大约要进行80MS左右
console.log(4);
func1().then(result => {
console.log(5);
});
func2().then(result => {
console.log(6);
});
setTimeout(() => {
console.log(7);
}, 0);
console.log(8);
复制代码