Promise教程(二)
第三章:Promise的优势
then的链式调用
Promise实例.then()返回的是一个【新的Promise实例】,它的值和状态由什么决定?
-
简单表达:由then()所指定的回调函数执行的结果决定
-
详细表达:
- 如果then所指定的回调返回的是非Promise值a,那么【新Promise实例】状态为:成功(fulfilled),成功的value为a
- 如果then所指定的回调返回的是一个Promise实例p,那么【新Promise实例】的状态、值,都与p一致
- 如果then所指定的回调抛出异常,那么【新Promise实例】状态为rejected,reason为抛出的那个异常
const p = new Promise((resolve,reject) => { setTimeout(() => { resolve(100) },1000) }) p.then( (value) => {console.log('sucess1',value); return Promise.reject('a')}, (reason) => {console.log('err1',reason)} ).then( (value) => {console.log('sucess2',value); return true}, (reason) => {console.log('error2',reason); return 100} ).then( (value) => {console.log('sucess3',value); return false}, (reason) => {console.log('error3',reason); return false} ).then( (value) => {console.log('sucess4',value); return -100}, (reason) => {console.log('error4',reason);} )
纯回调会引起的问题(回调地狱)
-
什么是回调地狱
函数作为参数层层嵌套,回调函数嵌套调用,,外部回调函数异步执行的结果是嵌套的回调函数执行的条件
-
回调地狱的原因是
回调地狱是由于糟糕的编码习惯造成的,功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,避免回调地狱的最重要的方面是将功能移开,保持代码简单,不嵌套并分成小模块,也就是多多进行代码封装
-
如何解决回调地狱
Promise的出现就是解决Node.js异步编程中回调地狱的问题
then的链式调用解决回调地狱(不是很优秀的解决方案)
//发送第一次请求 sendAjax('https://www.tianqiapi.com/free/day',{appid:23035354,appsecret:'8YvlPNrz'}) .then( value => { console.log('第一次请求成功',value) return sendAjax('https://www.tianqiapi.com/free/day',{appid:23035354,appsecret:'8YvlPNrz'}) }, reason => {console.log('第一次请求失败',reason)} ) .then( value => { console.log('第二次请求成功',value) return sendAjax('https://www.tianqiapi.com/free/day',{appid:23035354,appsecret:'8YvlPNrz'}) }, reason => {console.log('第二次请求失败',reason)} ) .then( value => { console.log('第三次请求成功',value) }, reason => {console.log('第三次请求失败',reason)} )
中断promise链:
-
当使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
-
办法:在失败的回调函数中返回一个pending状态的Promise实例
//发送第1次请求 sendAjax('https://api.apiopen.top/getJoke',{page:1}) .then( value => { console.log('第1次请求成功了',value); //发送第2次请求 return sendAjax('https://api.apiopen.top/getJoke2',{page:1}) }, reason => {console.log('第1次请求失败了',reason);return new Promise(()=>{})} ) .then( value => { console.log('第2次请求成功了',value); //发送第3次请求 return sendAjax('https://api.apiopen.top/getJoke',{page:1}) }, reason => {console.log('第2次请求失败了',reason);return new Promise(()=>{})} ) .then( value => {console.log('第3次请求成功了',value);}, reason => {console.log('第3次请求失败了',reason);} )
promise错误穿透
-
当使用promise的then链式调用时,可以在最后用catch指定一个失败的回调
-
前面任何操作出了错误,都会传到最后失败的回调中处理
备注:如果不存在then的链式调用,就不需要考虑then的错误穿透
//利用错误的穿透避免多次指定失败的回调 sendAjax('https://api.apiopen.top/getJoke2',{page:1}) .then( value => { console.log('第1次请求成功了',value); //发送第2次请求 return sendAjax('https://api.apiopen.top/getJoke',{page:1}) } ) .then( value => { console.log('第2次请求成功了',value); //发送第3次请求 return sendAjax('https://api.apiopen.top/getJoke',{page:1},3) } ) .then( value => {console.log('第3次请求成功了',value);} ) .catch( reason => {console.log(reason);} )
Promise的优势
- 指定回调函数的方式更加灵活:
- 旧的: 必须在启动异步任务前指定
- promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)
- 支持链式调用, 可以解决回调地狱问题
- 一个不是很优秀的解决方案:then的链式调用
- 终极解决方案:async/await(底层实际上依然使用then的链式调用)
第4章:async和await
async和await的使用
-
async修饰的函数
- 函数的返回值为Promise对象
- Promise实例的结果由async函数执行的返回值决定
-
await表达式
- await右侧的表达式一般为Promise实例对象,但也可以是其他的值
- 如果表达式是Promise实例对象,await后的返回值是promise成功的值
- 如果表达式是其它值,直接将此值作为await的返回值
-
注意:
- await必须写在async函数中,但async函数中可以没有await
- 如果await的Promise实例对象失败了,就会抛出异常,需要通过try…catch来捕获处理
const p = new Promise((resolve,reject) => { setTimeout(() => { resolve(1) },1000) }) ;(async () => { try{ const result = await p console.log(result) }catch(e){ //TODO handle the exception console.log(e) } })()
await的原理
-
若我们使用async配合await这种写法:
- 表面上不出现任何的回调函数
- 但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了
- 最终运行的代码是依然有回调的,只是程序员没有看见
const p = new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve('a') },4000) }) async function demo(){ //程序员“轻松”的写法 const result = await p console.log(result); console.log(100); console.log(200); //浏览器翻译后的代码 /* p.then( result => { console.log(result); console.log(100); console.log(200); }, ) */ } demo() console.log(1);
宏队列与微队列
- 宏队列:[宏任务1,宏任务2…]
- 微队列:[微任务1,微任务2…]
- 规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务
- 如果有,先执行微任务
- 如果没有,按照宏队列里任务的顺序,依次执行
//代码一
setTimeout(()=>{
console.log('timeout')
},0)
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('成功2',value)
)
console.log('主线程')
//代码二
setTimeout(()=>{
console.log('timeout1')
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(1).then(
value => console.log('成功1',value)
)
Promise.resolve(2).then(
value => console.log('成功2',value)
)
//代码三
setTimeout(()=>{
console.log('timeout1')
Promise.resolve(5).then(
value => console.log('成功了5')
)
})
setTimeout(()=>{
console.log('timeout2')
})
Promise.resolve(3).then(
value => console.log('成功了3')
)
Promise.resolve(4).then(
value => console.log('成功了4')
)