关于异步处理,ES5的回调使我们陷入地狱,ES6的Promise使我们脱离魔障,终于、ES7的async await带我们走向光明。
es5中的回调函数指的是一个函数作为参数传递到另一个函数中,这个作为参数的函数就是回调函数。
Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:
f1().then(f2);
require ('fs').readFile('./index.html', (err,data) => {
})
$.get('/server', (data) => {
})
setTimeout(() => {
}, 2000)
以上内容使用的是纯回调函数来进行异步操作
Promise是es6引入异步编程的新的解决方案
语法上:一个构造函数。可以实例化对象,封装异步操作,获取成功和失败的结果
1)指定回调函数的方式更加灵活
旧的:必须在启动异步任务前指定
promise:启动异步任务 =》返回promise对象 =》给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
2)支持链式调用,可以解决回调地狱问题
asyncFunc1(opt, (...args1) => {
asyncFunc2(opt, (...args2) => {
asyncFunc3(opt, (...args3) => {
asyncFunc4(opt, (...args4) => {
//some operation
})
})
})
})
回调地狱:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件
回调地狱的缺点:不便于阅读,不便于异常处理
案例需求1:点击抽奖按钮,等2秒钟,告知是否中奖,中奖概率定位30%,中奖显示弹框,“恭喜您中奖了”,未中奖,弹出框“再接再厉”
案例需求2:promise实现Ajax请求
案例需求3:promise封装Ajax请求
实例对象中的一个属性 【PromiseState】属性是内置的,我们不能直接对其进行操作
*pending 未决定的
*resolved / fulfilled 成功
*rejected 失败
promise的状态改变
1.pending ===> resolved
2.pending ===>rejected
说明:只有这2种,且一个Promise对象只能改变一次
无论变为成功还是失败,都会有一个结果数据
成功的结果数据一般称为value,失败的结果数据一般称为reason
示例对象中的另一个属性 【PromiseResult】
保存着异步任务 [成功 / 失败] 的结果
resolve 和 reject 函数可以对此属性进行修改
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RaAbrfsr-1607945193404)(C:\Users\sundan\AppData\Roaming\Typora\typora-user-images\image-20201205190541879.png)]
1.Promise的构造函数:Promise(executor){}
(1)executor函数:执行器 (resolve, reject) => {} //可以使用箭头函数或匿名函数
(2)resolve函数:内部定义成功时我们调用的函数 value => {}
(3)reject函数:内部定义失败时我们调用的函数 reason => {}
说明:执行器函数会在promise内部立即同步调用,异步操作在执行器中执行
let p = new Promise((resovle, reject) => {
console.log(11111)
})
console.log(22222)
2.Promise.prototype.then方法:(onResolved, onRejected) => {} //用来指定回调
(1)onResolved函数:成功的回调函数 (value) => {}
(2)onRejected函数: 失败的回调函数 (reason) => {}
说明:指定用于得到的成功value的成功回调和用于得到失败reason的失败回调
返回一个新的Promise对象
3.Promise.prototype.catch方法:(onRejected) => {}
(1)onRejected函数:失败的回调函数 (reason) => {}
let p = new Promise((resovle, reject) => {
//修改 promise的状态
reject('error')
})
p.catch(reason => {
console.log(reason)
})
4.Promise.prototype.finally方法:() => {}
finally()方法的回调函数不接受任何参数,不管promise最后的状态,在执行完then或catch指定的回调函数后,都会执行finally方法指定的回调函数
5.Promise.resolve()方法(value) => {}
(1)value:成功的数据或promise对象
说明:返回一个成功/失败的promise对象(可以快速得到一个promise对象/封装一个值,将此值转化为promise对象)
let p1 = Promise.resolve(521)
console.log(p1)
//如果传入的参数为非Promise类型的对象,则返回的结果为成功
//如果传入的参数为Promise对象,则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('ok')
reject('error')
}))
p2.catch(reason => {
console.log(reason)
})
6.Promise.reject()方法(reason) => {}
(1)reason:失败的原因
说明:返回一个失败的promise对象
let p3 = Promise.reject(521)
console.log(p3)
let p4 = Promise.reject(new Promise((resolve, reject) => {
resolve('ok')
}))
console.log(p4)
7.Promise.all()方法(promise) => {}
(1)promise:包含n个promise的数组
说明:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败
let p1 = new Promise(((resolve, reject) => {
resolve('ok')
}))
// let p2 = Promise.resolve('success')
let p2 = Promise.reject('error')
let p3 = Promise.resolve('successful')
const result = Promise.all([p1,p2,p3])
console.log(result)
8.Promise.race()方法(promises) => {}
(1)promise:包含n个promise的数组
说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态
let p1 = new Promise((resolve, reject) => {
// resolve('ok')
setTimeout(() => {
resolve('ok')
},1000)
})
let p2 = Promise.resolve('success')
let p3 = Promise.resolve('successful')
const result = Promise.race([p1,p2,p3])
console.log(result)
let p = new Promise((resolve, reject) => {
//1.resolve函数
//resolve('ok') //pending ===> fulfilled(resolved)
//2.reject函数
//reject('error') //pending ===> rejected
//3. 抛出错误
throw '出问题了'
})
console.log(p)
(1)都有可能,正常情况下是先指定回调再改变状态,但也可以先改变状态再指定回调
(2)如何先改变状态在指定回调?
1)在执行器中直接调用resolve/reject
2)延迟更长时间才调用then()
(3)什么时候才能得到数据?
1)若先指定回调,当状态改变时,回调函数就会调用,得到数据
2)若先改变状态,当指定回调时,回调函数就会调用,得到数据
let p = new Promise((resolve, reject) => {
resolve('ok')
})
let result = p.then(value => {
//console.log(value)
//1.抛出错误
//throw '除了问题'
//2.返回结果是非Promise类型的对象
// return 521
// 3.返回结果是Promise对象
return new Promise((resolve,reject) => {
// resolve('success')
reject('error')
})
}, reason => {
console.warn(reason)
})
console.log(result)
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
},1000)
})
p.then(value => {
return new Promise((resolve, reject) => {
resolve('success')
})
}).then(value => {
console.log(value)
}).then(value => {
console.log(value)
})
可以只在指定失败的回调,其他地方不用指定失败的回调
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
// reject('error')
},1000)
})
p.then(value => {
throw '出错啦'
// console.log(111)
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => {
console.warn(reason)
})
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok')
// reject('error')
},1000)
})
p.then(value => {
console.log(111)
//有且只有一个方式
return new Promise(() => {
}) //pending状态的Promise,和上方的promise状态相比没有变化,故不执行后面的回调函数
}).then(value => {
console.log(222)
}).then(value => {
console.log(333)
}).catch(reason => {
console.warn(reason)
})
1、无法取消Promise,一旦新建它就会立即执行,无法中途取消。
2、如果不设置回调函数,promise内部抛出的错误,不会反应到外部。
3、当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
1.解决回调地狱(Callback Hell)问题
2.更好地进行错误捕获
多重嵌套 callback 除了会造成上面讲的代码缩进问题,更可怕的是可能会造成无法捕获异常或异常捕获不可控。
1)比如下面代码我们使用 setTimeout 模拟异步操作,在其中抛出了个异常。但由于异步回调中,回调函数的执行栈与原函数分离开,导致外部无法抓住异常。
function fetch(callback) {
setTimeout(() => {
throw Error('请求失败')
}, 2000)
}
try {
fetch(() => {
console.log('请求处理') // 永远不会执行
})
} catch (error) {
console.log('触发异常', error) // 永远不会执行
}
// 程序崩溃
// Uncaught Error: 请求失败
(2)如果使用 promises 的话,通过 reject 方法把 Promise 的状态置为 rejected,这样我们在 then 中就能捕捉到,然后执行“失败”情况的回调。
function fetch(callback) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject('请求失败');
}, 2000)
})
}
fetch()
.then(
function(data){
console.log('请求处理');
console.log(data);
},
function(reason, data){
console.log('触发异常');
console.log(reason);
}
);
async await是promise和generator的语法糖
(1)函数的返回值为promise对象
(2)promise对象的结果由async函数执行的返回值决定
//和then方法的返回规则一样
async function main(){
//1.如果返回的是一个非Promise类型的数据
// return 521
//2.如果返回的是一个Promise对象
// return new Promise((resolve, reject) => {
// // resolve('ok')
// reject('Error')
// })
//3.抛出异常
throw "oh no"
}
let result = main()
console.log(result)
(1)await右侧的表达式一般为Promise对象,但也可以是其他的值
(2)如果表达式是promise对象,await返回的是promise成功的值
(3)如果表达式是其他的值,直接将此值作为await的返回值
注意:
(1)await必须写在async函数中,但async函数中可以没有await
(2)如果await的promise失败了,就会抛出异常,需要通过try…catch捕获处理
案例:若有三个请求需要发生,第三个请求是依赖于第二个请求的结果第二个请求依赖于第一个请求的结果。若用 ES5实现会有3层的回调,若用Promise 实现至少需要3个then。一个是代码横向发展,另一个是纵向发展,若使用 async和await
//我们仍然使用 setTimeout 来模拟异步请求
function sleep(second, param) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(param);
}, second);
})
}
async function test() {
let result1 = await sleep(2000, 'req01');
let result2 = await sleep(1000, 'req02' + result1);
let result3 = await sleep(500, 'req03' + result2);
console.log(`
${
result3}
${
result2}
${
result1}
`);
}
test();
//req03req02req01
//req02req01
//req01
执行顺序:
(1)await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成;
(2)若 Promise 正常处理(Resolved,又称Fulfilled),其回调resolve函数的参数作为 await 表达式的值,继续执行 async function;
(3)若 Promise 处理异常(Rejected),await 表达式会把 Promise 的异常原因抛出;