Promise是什么?
什么是回调地狱 ?
回调函数
异步任务
回调地狱
Promise解决回调地狱
Promise对象
prototype
PromiseState
PromiseResult
Promise.prototype属性
then
catch
finally
Promise.all()
Promise.race()
Promise.resolve()
Promise.reject()
async/await
async
await
promise 是es6中新增加对象,产生是为了解决异步回调地狱问题。利用then函数的链式调用解决了异步回调地狱问题。
当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有当满足一定条件后该函数才可以执行,这种函数就称为回调函数。setTimeout就是最常见的回调函数。
setTimeout(function(){ //function(){console.log('执行了回调函数')}就是回调函数,它只有在3秒后才会执行
console.log('执行了回调函数');
},3000) //3000毫秒
与之相对应的概念是“同步任务”,同步任务在主线程上排队执行,只有前一个任务执行完毕,才能执行下一个任务。异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。同样,还拿定时器作为异步任务举例:
setTimeout(function(){
console.log('执行了回调函数');
},3000)
console.log('111');
这种不阻塞后面任务执行的任务就叫做异步任务。
接下来让我们看看什么是回调地狱。
setTimeout(function () { //第一层
console.log('111');
setTimeout(function () { //第二程
console.log('222');
setTimeout(function () { //第三层
console.log('333');
}, 1000)
}, 2000)
}, 3000)
可以看到,代码中的回调函数套回调函数,居然套了3层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。
总结一下,回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
那该如何解决回调地狱呢?
Promise是js中的一个原生对象,是一种异步编程的解决方案,可以替换掉传统的回调函数解决方案。
- Promise构造函数接收一个函数作为参数,我们需要处理的异步任务就卸载该函数体内,该函数的两个参数是resolve,reject。异步任务执行成功时调用resolve函数返回结果,反之调用reject。
- Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。
- Promise的链式编程可以保证代码的执行顺序,前提是每一次在than做完处理后,一定要return一个Promise对象,这样才能在下一次then时接收到数据。
console.log(1);
setTimeout(() => {
console.log(6);
}, 0);
let p = new Promise((resolve, reject) => {
// 1:该函数是不是回调函数? 是
// 2:该函数是同步执行还是异步执行? 同步执行
// 3:话说promise实例对象是为了解决异步回调地狱问题?究竟哪里的程序是异步程序呢?then 的回调函数异步执行
console.log(2);
// 1 resolve 是触发 then实参一函数的条件之一
// 2 resolve 同步触发函数,但是 then的回调函数是异步触发的
// 因为then的回调函数,在Promise内部有多个执行条件,resolve执行只是条件之一
resolve()
// reject 是触发then 实参二函数的条件之一,也是触发catch的实参函数条件之一。
// reject()
})
console.log(3);
p.then(() => {
console.log(4); // 异步执行的
}, () => {
console.log('reject 触发');
})
console.log(5);
//执行结果为:1,2,3,5,4,6
// 为什么setTimeOut 回调执行,比 then 的回调慢?
// then回调是微任务。setTimeOut是宏任务。在忽略Script情况下先微任务后宏任务。
总结:
- Peomise的回调函数是同步执行的,只有then的回调的异步的
- then()方法有两个参数,第一个是resolve成功的回调,第二个是reject失败的回调
- 当宏任务和微任务同时异步时,在忽略Script的情况下,先执行微任务再执行宏任务
Promise对象上有三个属性
- [[prototype]]
- [[PromiseState]]
- [[PromiseResult]]
[[Prototype]] 是Promise的原型,属性为:
constructor:指向构造函数Promise
then:当实例状态变为成功的时候,调用then函数
catch:当实例的状态变为失败的时候,调用catch函数
finally:当实例为未完成状态变为已完成状态的时候调用的方法
[[PromiseState]]为Promise的状态,有三种:
- 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled):意味着操作成功完成。
- 已拒绝(rejected):意味着操作失败。
当调用Promise对象的resolve()方法时,Promise的状态 [[PromiseState]]由pending变为fulfilled,当调用Promise对象的reject()方法时,Promise的状态 [[PromiseState]]由pending变为rejected
[[PromiseResult]] 记录Promise的运算结果,初始值为resolve()方法的参数,没有参数则为undefind,只能通过then()方法获取,通过then方法的return进行赋值
获取PromiseResult
let p = new Promise((resolve,reject)=>{
resolve(123)
})
p.then(val=>{
console.log('读取p对象中[[PromiseResul]]的赋值---->',val);
})
给PromiseResult赋值
let p1 = p.then((res)=>{
return '给p1.[[PromiseResult]]赋值'
},()=>{
console.log('reject');
})
console.log(p1);
Promise.prototype有三个api: then catch finally
then:因为Promise的then()方法返回的是一个Promise实例,所以可以通过then()进行链式调用
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(123)
},1000)
})
let p1 = p.then(res=>{ // res=? 123
return res+1
}).then(res=>{// res=? 124
return 'ni'+res
}).then(res=>{ // res=? ni1234
return 'hao'+res
})
catch:1 捕获.then链式调用中的错误
2 统一 处理 promis对象 的 已经拒绝的状态
let p = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(123)
},1000)
})
let p1 = p.then(res=>{ // res=? 123
return res+1
}).then(res=>{// res=? 124
return 'ni'+res
}).then(res=>{ // res=? ni1234
return 'hao'+res
}).catch((err)=>{
console.log(err);
})
finally:用于指定不管 Promise 对象最后状态如何,都会执行的操作
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123)
}, 1000)
})
let p1 = p.then(res=>{
console.log(1);
}).then(res=>{
console.log(2);
}).then(res=>{
console.log(3);
}).catch((err)=>{
console.log(4);
console.log(err);
}).finally(()=>{
console.log('finally');
})
.then(res=>{
console.log('验证finally后是否能够继续then');
})
console.log(p1);
作用:可以同时触发多个promise,用于处理并发任务,等多个异步都结束的在执行下一个任务
参数:元素:元素promise实例对象
返回值:全新的promise实例 [[PromiseResult]]:[p结果,p2结果,p3结果]
then 三个实例对象都变为 已接受状态 时候触发回调函数
回参:数组:数组元素为三个实例对象的[[promiseResult]],话句话三个实例对象的运算结果
let p = new Promise((resolve,reject)=>{
console.log('第一个异步任务开始了');
setTimeout(() => {
resolve('第一次异步结果')
}, 1000);
})
let p2 = new Promise((resolve,reject)=>{
console.log('第二个异步任务开始了');
setTimeout(() => {
resolve('第二次异步结果')
// reject()
}, 2000);
})
let p3 = new Promise((resolve,reject)=>{
console.log('第三个异步任务开始');
setTimeout(() => {
resolve('第三次异步结果')
}, 3000);
})
let state = new Date().getTime();
Promise.all([p,p2,p3]).then(arr=>{
let timer = (new Date().getTime()) - state;
console.log('三个异步都结束了');
console.log(arr);
console.log(timer);
},()=>{
console.log('有一个拒绝的');
})
Promise.race()
作用:多个promise实例,只要接收第一个变为 已完成.
参数:元组<promise实例>
返回: 新的promise实例
注意: 同时触发多个promise , 只要有一个状态变为 已完成,返回的promise就是,已完成状态的promise,并且[[PromiseResult]]的值为 该已完成状态 的值
用途:多个异步任务中;可以找到第一个成功的异步任务,一级异步运算结果
var promise1 = function () {
return new Promise(function (resolve) {
setTimeout(function () {
console.log(1);
resolve(1);
}, 1000)
});
}
var promise2 = function () {
return new Promise(function (resolve,reject) {
setTimeout(function () {
console.log(2);
resolve(2);
}, 2000);
});
}
Promise.race([promise1(), promise2()])
.then(function (val) {
console.log('有⼀个 promise 状态已经改变', val);
})
all race 区别
- 相同点:都是返回promise,作用:捕获异步结果
- all 多个异步同时触发,最后一个成功,all的状变成功,捕获到 所有异步运算结果[[PromiseResult]]
- race 多个异步同时触发,第一个成功,race的状态变成功,捕获到 第一个异步运算结果 [[PromiseResult]]
Promise.resolve()
作用:创建一个 已完成状态的promise实例
返回值:promise实例
实参: 作用: 给[[PromiseReuslt]] 赋值
var promise = Promise.resolve(123);
console.log(promise);
promise
.then(function (val) {
console.log('已完成', val);
});
Promise.reject()
作用:创建一个已拒绝状态的promise 实例
返回值:promise实例
实参: 赋值错误对象
var promise = Promise.reject(new Error('已经拒绝'));
console.log(promise);
promise
.then(null,function (val) {
console.log('已拒绝', val);
});
async/await其实是Promise的语法糖,它能实现的效果都能用then链来实现,这也和我们之前提到的一样,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await译为等待,所以我们很好理解async声明function是异步的,await等待某个操作完成。
作为一个关键字放在函数的前面,表示该函数是一个异步函数,意味着该函数的执行不会阻塞后面代码的执行 异步函数的调用跟普通函数一样
async function asy(){
return "hello";
}
console.log(asy());
// Promise { 'hello' }
可以看出asy函数的返回结果是一个Promise对象,要获取Promise的返回值应该用then方法
async function asy(){
return "hello";
}
asy().then((result)=>{
console.log(result);
});
console.log("world");
// world
// hello
此时先输出的就是后面的字符串’world‘,说明异步函数的执行没有阻塞后面的代码执行,
async
的内部实现原理就是如果该函数中有一个返回值,当调用该函数时,默认会把return后面直接量通过Promise.resolve()返回Promise对象,若函数内部抛出错误,则调用Promise.reject()
返回一个Promise
对象,因为返回的是一个Promise对象,所以可以使用Promise的所有用法,例如:then,catch,finally
- 按照语法说明,await等待的是一个Promise对象,或者是其他值(也就是说可以等待任何值),如果等待的是Promise对象,则返回Promise的处理结果;如果是其他值,则返回该值本身。
- await会暂停当前async function的执行,等待Promise的处理完成。若Promise正常处理(fulfillded),其将回调的resolve函数参数作为await表达式的值,继续执行async function;若Promise处理异常(rejected),await表达式会把Promise异常原因抛出;另外如果await操作符后面的表达式不是一个Promise对象,则返回该值本身。
function test1(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function test2(){
let result = await test1('hello world');
console.log(result); // 3秒钟之后出现hello world
}
test2();
上面可以看出await等待的是一个Promise,并且处理结果为resolve,所以返回的是'hello world'
function test1(x){
return new Promise(resolve=>{setTimeout(() => {
resolve(x);
}, 3000)
}
)
}
async function test2(){
let result = await test1('123');
console.log(result); // 3秒钟之后出现123
console.log('456') // 3秒钟之后出现456
}
test2();
console.log('789') //立即输出789
这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。await暂停当前async的执行,所以'789''最先输出,'123'和‘456’是3秒钟后同时出现的。
与Promise对比不再需要多层.then方法,假设一个业务分很多步骤完成,并且每个步骤都是异步,依赖上一个步骤的结果。
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
// Promise方式
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// async await方式
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
//执行结果为:
//step1 with 300
//step2 with 500
//step3 with 700
//result is 900
//doIt: 1510.2490234375ms