在之前的博文中,我们了解了,Js是一门单线程语言,不过存在一些异步执行的方法,它们会放到callback queue 中,等主线程stack中任务执行完成,再去执行callback queue 。 但是如果需要callback queue中的任务返回的数据怎么办,比如下边的回调金字塔
什么是回调金字塔
-
举例说明
存在一个需求,比如需要拿到回调中数据,根据数据判断是否显示一个banner图,这就要求将banner图的显示放到ajax回调成功返回的函数中,这种写法就称为 “回调金字塔” -
比如下边代码
// 伪代码表示 ajax('xxx',function (){ createDom(); btn.onclick = function (){ ajax('xxx',function(){ // .... bc.onclick = function (){ ajax('xxx',function(){ //... }) } }) } })
代码中,在ajax返回成功之后,去创建dom并且绑定了一个点击事件。这样写法是没语法问题的,但是存在 代码结构复杂,不利于阅读的问题,为了解决这种问题,引入Promise
什么是 Promise
Promise是 Promises/A+规范 提供,后来被ES6 收录为标准
Promise 异步编程的一种解决方案,是一个构造函数 , 可以理解为用写同步代码的方式编写异步代码
-
Promise 语法
基本写法new Promise(function(resolve,reject){...})
参数 是一个函数 ,函数有两个参数
- 参数1:resolve函数,调用后会把Promise的状态从pending变为resolved
- 参数2:reject函数,调用后会把Promise的状态从pending变为rejected
注意
- 1、两个函数都可以传参,参数代表异步操作的结果
- 2、两个函数调用后会执行then方法的两个参数(对应执行)
-
promise对象 : 对象如下图
var p = new Promise(function(resolve,reject){}) console.dir(p) // 查看p对象的状态
截图可以看出 Promise是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;不过,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套
Promise对象存在状态。几个状态如下
- pending 初始状态,既不是成功,也不是失败
- fulfilled 操作成功
- rejected 操作失败
-
函数可以修改状态
resolve和 reject会改变Promise对象的状态,比如下边代码执行之后,状态改为resolved//resolve 调用后会把Promise的状态从pending变为resolved //reject函数,调用后会把Promise的状态从pending变为rejected var p = new Promise(function(resolve,reject){ resolve(); }) console.dir(p) // 查看p对象的状态
注意 :
- 状态只能改变一次,要么从初始变为成功,要么从初始变为失败。并且改变后就不会再变了
- 如果状态为fulfilled或者rejected时又可以称为resolved状态(已定型)
-
resolve 和 reject 分别对应then中第一个、第二个函数
const p=new Promise((resolve,reject)=>{ //resolve('状态变为成功,会调用then里的第一个参数'); reject('状态变为失败,会调用then里的第二个参数'); }).then( value=>{ console.log(value); //状态变为成功,会调用then里的第一个参数 }, error=>{ console.log(error); //状态变为失败,会调用then里的第二个参数 } ); /** const p=new Promise(function(resolve,reject){ //resolve('状态变为成功,会调用then里的第一个参数'); reject('状态变为失败,会调用then里的第二个参数'); }).then( function(value){ console.log(value); //状态变为成功,会调用then里的第一个参数 }, function(error){ console.log(error); //状态变为失败,会调用then里的第二个参数 } ); **/
上边代码定义了一个promise对象,它的then里边存在两个函数,至于走哪一个函数是根据Promise对象里边的函数(如下Js)决定的,如果promise中定义的函数最终执行的是resolve(第一个参数),就会走then中第一个函数,如果选择的是reject就会走then中第二个函数
function(resolve,reject){ //resolve('状态变为成功,会调用then里的第一个参数'); reject('状态变为失败,会调用then里的第二个参数'); }
then 方法可以被上边调用原因
-
promise中then方法原理
之前提到了, 当我们选择resolve时,会去走then中第一个方法,选择reject会走then中第二个方法,那么为什么这样呢,或者说,Js是如何做到这一点的,其实这和Promise原型设计有关then方法时直接写在Promise的原型之中,源码如下
Promise.prototype.then(onFulfilled,onRejected)
- onFulfilled,当调用resolve的时候,该函数被执行(处理成功状态)
- onRejected(可选),当调用reject的时候,该函数被执行(处理失败状态)
Promise.then方法链式调用
- 什么是链式调用
链式调用,就是可以在promise对象.then()之后,如果有需求可以继续调用then。因为then方法返回值就是promise对象,比如下边的代码console.dir(p.then()); // 输出依然是个Promise对象 console.log(p==p.then()); //false
- 用处
解决之前提到的调用金字塔问题,比如下边代码 (注意用伪码写的,post地址不存在)
上边代码中,第一次调用then处理是对传参 * 2 ; 第二次调用时打印值。最终输出结果也证明了then可以实现链式调用, 实际上var p = new Promise(function(resolve, reject){ $.post("demo_test.html",function(data,status){ if(data.age > 13){ resolve(13) } }); }); p.then(function(value){ console.log(value); return value*2; }).then(function(value){ console.log(value); })
Promise中catch函数
-
catch函数用法
Promise.prototype.catch(onRejected) , 作用与then里的onRejected一样,都用于指定发生错误时的回调函数, 建议用catch方法代替then的第二个参数。这样能捕获前面then方法执行时候的错误