ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
  在JavaScript中,回调函数是处理异步编程的常用解决方案,但层层嵌套的回调金字塔(如下代码所示)一直受人诟病,因为不仅在视觉上更加混乱,而且在管理上也更为复杂。 ~~~ setTimeout(() => { var reason = "成功执行"; setTimeout(() => { console.log(reason); }, 500); }, 500); ~~~   Promise是ES6新增的特性,能更合理的控制和追踪异步操作。它是一个包含状态、可继承的对象,不仅能管理而不是依赖回调,还能以同步的方式传递异步的计算结果,从而避免陷入回调金字塔的泥潭中。链式(即串联起来)的Promise让代码有更高的可读性和更便捷的调试性。下面会用Promise实现上一个示例的功能。 ~~~ var promise = new Promise(function(resolve, reject) { setTimeout(() => { resolve("成功执行"); }, 500); }); promise.then(function(value) { setTimeout(() => { console.log(value); }, 500); }); ~~~   示例只是为了能对Promise有个初步的认识,其中涉及的Promise的创建、then()方法等概念,都将在接下来的章节中做详细的讲解。 ## 一、状态   Promise依据其状态的变化,让异步操作变得有序,而Promise有三种互斥的状态可供选择,具体如下所列。   (1)pending:等待中,初始状态,此时还未处理(Promise中的)异步操作。   (2)fulfilled:已完成,异步操作成功时的状态。   (3)rejected:已拒绝,异步操作失败时的状态。   每个Promise只能维护一个状态,并且状态只会朝一个方向变化,即从pending变为fulfilled或rejected,而fulfilled不能变为rejected,反之也一样,这种处理状态的行为也叫决议。注意,Promise会在内部处理状态的变化,并且由于ES6对外没有暴露访问Promise状态的属性或方法,因此无法在外部判断Promise当前处在哪个状态。 ## 二、创建   如果要使用Promise,那么需要先初始化,可以通过构造函数的方式创建一个Promise实例,如下所示。 ~~~ var promise = new Promise(function(resolve, reject) { /* executor */ }); ~~~   构造函数Promise()能接收一个执行器(executor),即带有resolve和reject两个参数的函数,执行器会在构造函数返回新实例前被调用。它的两个参数也是函数,分别适合不同的语境,具体如下所列。   (1)在执行器中的异步操作完成时会调用resolve()函数,当前Promise的状态会根据它的参数发生变化。当参数为空或非Promise时,当前状态变成fulfilled;当参数是Promise时,当前Promise的状态和参数的相同。   (2)在执行器中的异步操作错误时会调用reject()函数,当前Promise的状态会变成rejected。   resolve()和reject()都能接收一个参数(即决议结果),前者的参数可以是本次操作的结果;而后者的参数可以是操作失败的理由,它们都会传递给下一个异步操作。 ## 三、then()   在生成Promise实例之后,就能通过then()方法绑定状态变化后的回调函数(即处理方法),如下代码所示,此处是异步操作同步化的关键。 ~~~ promise.then(function(value) { // success }, function(reason) { // failure }); ~~~   then()方法的两个参数,可分别指定状态变成fulfilled和rejected后的回调函数,而这两个回调函数的参数分别来自于resolve()和reject()函数。通过then()方法的这两个回调函数就能清晰的反馈出异步操作是否成功执行了。   由于then()方法的返回值是一个新的Promise实例,因此可以链式调用then()方法,按顺序绑定回调函数,如下所示。 ~~~ var chain = new Promise(function(resolve, reject) { reject("error"); }); chain.then(null, function(reason) { console.log(reason); //"error" return "end"; }) .then(function(value) { console.log(value); //"end" }); ~~~   虽然第一个then()方法中的已完成的回调函数是null,但并不会终止数据的传递,仍然是先输出“error”,再输出“end”。之所以是这样的输出顺序,与回调函数的执行顺序有关。在then()方法链中,当前Promise的状态会决定下一个then()方法执行哪个回调函数,而这个状态又会受回调函数和它的返回值的影响,具体如下所列。   (1)当返回值是一个非Promise的值时,其状态会变成fulfilled。   (2)当回调函数抛出一个错误时,其状态会变成rejected。   (3)当返回值是一个Promise时,其状态与返回值的相同。   下面有一个示例,描述了第三种情况,其中Promise.resolve()创建了已完成的Promise,相当于新创建一个在执行器中调用resolve()函数的Promise;Promise.reject()创建了已拒绝的Promise;catch()方法能处理拒绝的回调函数。这些都将在随后的章节中做详细介绍。 ~~~ var chain = new Promise(function(resolve, reject) { resolve(); }); chain.then(function(value) { return Promise.resolve("fulfilled"); //相当于 return new Promise(function(resolve) { resolve("fulfilled"); }); }) .then(function(value) { console.log(value); //"fulfilled" return Promise.reject("rejected"); }) .catch(function(reason) { console.log(reason); //"rejected" }); ~~~ ## 四、thenable   包含then()方法的对象被称为thenable,所有的Promise都是thenable,下面是一个自定义的thenable,then()方法的参数含义与Promise中的相同。 ~~~ let tha = { then(resolve, reject) { reject("thenable"); } }; ~~~   Promise.resolve()能接收一个thenable,并返回一个新的Promise实例,将上一个示例的tha对象传递给它,如下所示。 ~~~ Promise.resolve(tha) .catch(function(reason) { console.log(reason); //thenable }).then(function() { console.log("end"); }); ~~~   Promise.resolve()能将thenable转换成已完成或已拒绝的Promise,其最终的状态取决于thenable的then()方法,像这个示例中的then()方法调用了reject()函数,因此新的Promise的状态是已拒绝(rejected)。 ## 五、错误处理   Promise的catch()方法可以捕获并处理前一个异步操作中抛出的错误,它能接收一个已拒绝的回调函数(onRejected),其行为相当于调用一个忽略已完成的回调函数的then()方法,例如像下面这样第一个参数传null或undefined。 ~~~ catch(onRejected) //相当于 then(null, onRejected) then(undefined, onRejected) ~~~   下面是一个使用了catch()方法的例子,先在执行器中抛出一个错误,然后在catch()方法中处理。 ~~~ var error = new Promise(function(resolve, reject) { throw "error info"; }); error.catch(function(reason) { console.log(reason); //"error info" }); ~~~   如果Promise的状态在抛出错误之前被改变,那么这个错误就不能被catch()方法捕获,如下所示。 ~~~ var error = new Promise(function(resolve, reject) { resolve(); throw "error info"; }); error.catch(function(reason) { console.log(reason); //不会输出 }); ~~~   在执行器中,由于resolve()函数在throw语句之前被调用,因此“error info”这句错误理由就不能在catch()方法中输出。   在链式的Promise中,一旦发生错误,那么这个错误在没被捕获前,会一直传递下去。为了确保所有的错误都能被处理,可在链的末尾加上catch()方法。 ***** > 原文出处: [博客园-ES6躬行记](https://www.cnblogs.com/strick/category/1372951.html) [知乎专栏-ES6躬行记](https://zhuanlan.zhihu.com/pwes6) 已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。 ![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200) 推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。