为什么要有Promise
“回调地狱”这个词大家应该不陌生,Promise的存在很大一部分原因也是要为我们解决回调地狱的问题,但是这个词可能不是你想的那么简单。
首先多层嵌套这一个因素会导致代码稍微难以读懂,我这里为什么要用“稍微”呢,因为回调地狱并不仅仅是因为嵌套。
更多的是因为传统的嵌套回调中的方法不能只考虑正常执行,要考虑可能导致步骤执行顺序偏离异常的情况,如果我们强行使用手工硬编码(将错误处理方式直接嵌入到程序)来处理,加之有多层嵌套,最终肯定会导致代码的复杂,以至于无法维护和更新。
这句话对我而言算是醍醐灌顶,因为我之前将回调地狱仅仅理解为多层嵌套导致代码不易读。
Promise如何解决问题
-
“神奇”的
then
方法每次
Promise
对象调用then()
都会创建并返回一个新的Promise,这代表着什么呢?你可以在then()
方法后面继续调用then()
,上一个Prmoise
的参数也可以链式传递,这也更符合人类线性思维的方式。var p = Promise.resolve(21) p.then(function (v) { console.log(v) // 21 return v++ }).then(function(v) { console.log(v) // 22 })...
这样就完美解决了层层嵌套的问题,如果后面的步骤需要等待前面步骤来完成一些事情(比如网络访问)后再调用,要怎么处理呢?
var p = Promise.resolve(21) p.then(function (v) { console.log(v) // 21 return new Promise( function(resolve, reject) { // 一些耗时操作 resolve(v++) }) }).then(function(v) { console.log(v) // 22 })...
使用
retuan
语句可以立即完成链接的Promise
,可以理解为“顶替”了then()
方法本来要反馈的promise
.-
特殊的异常处理
传统的
try...catch
只能处理同步的错误。function foo() { setTimeout( function() { obj.error(); // obj并未提前声明,此处会抛出异常 } ) } try { foo() } catch(err) { // 上述代码的错误会抛出全局的错误,并不会在catch中被捕获 }
Promise
的异常捕获机制,promise
一旦决议,即使在success
中产生的异常,也不会触发error
函数,这也是promise
结果不可变的特性所在,错误会传递给链中的下一个promise
,catch
作为异常捕获点,会在最终将错误捕获。为了避免对上述解释产生一些误解,请参照下方实践:1、catch接收成功函数错误信息 var p = Promise.resolve(20) p.then(function success(res) { console.log(res.toLowerCase()) },function error(){ // 不会在这里报错 }).catch(err => { console.log(err) // TypeError: res.toLowerCase is not a function }) 2、catch接收上一个promise抛出的异常 var p = Promise.resolve(20) p.then(function success(res) { console.log(res.toLowerCase()) }).then(function success(res) {}, function error(res){return res}) // 第一个then报错信息会触发本次promise,导致触发错误的回调函数 .catch(err => { // 最终捕获上一个promise(第二个then)抛出的异常 console.log(err) // Promise {<fulfilled>: TypeError: res.toLowerCase is not a function }) 3、catch接收上一个promise默认抛出的异常 如果没有定义error函数呢?如下所示,默认的处理函数会把错误继续冒泡至外层promise[此处为catch函数] var p = Promise.resolve(20) p.then(function success(res) { console.log(res.toLowerCase()) }).then(function success(res) {}) .catch(err => { console.log(err) // TypeError: res.toLowerCase is not a function })
-
关于Promise函数异步执行的顺序
一个Promise
决议后,这个Promise
上所有通过.then
注册的回调都会在下一个异步时机点上被立即调用。
关于异步时机点,我们知道JavaScript是有事件循环队列的,在es6
中有一个新的概念建立在事件循环队列之上,叫做任务队列。他是挂载时间循环队列每个tick之后的队列,直观点理解就是当前事件循环执行完成之后,可能出现的异步动作(Promise)不会添加到事件循环队列中,而是添加到当前事件的任务队列中。
事件循环(宏,一般包括同步代码,IO,setTimeout
, setInterval
)和任务队列(微,可以理解为特指Promise)执行规则如下:
执行宏任务循环 -> 执行微任务队列 -> UI render
(可选) -> 执行下一个宏任务循环 -> 执行微任务队列 …以此顺序,具体实践请查看下方代码:
console.log('A') // 同步代码,第一轮队列中马上执行
setTimeout(function() {
console.log('B') // 此代码会加入第二轮宏任务循环
},0);
Promise.resolve().then(function() {
console.log('C') // promise,第一轮微任务执行
setTimeout(function() {
console.log('R') // 此代码加入第二轮宏任务
},0)
}).then(function() {
console.log('D') // promise,第一轮微任务执行
}) // 因为没有其他异步操作,所以都会直接加入任务队列中,如果一直.then下去,甚至可以无限循环,无法转移到下一个事件循环中
console.log('E') // 同步代码,第一轮队列执行
按照上述解释,打印的结果分别应为 A E C D B R
参考文献:
《你不知道的JavaScript》 中卷