1

异步编程Promise绝对是ECMAscript2015(ES6)中的一个难点。如何打败它?

第一步:为什么要用Promise?

那肯定是我们之前异步编程方式存在着种种问题
1.回调地狱,过多的嵌套回调让代码毫无可读性,不利于调试和维护。
就行下面这种情况,每次发送Ajax请求都需要依赖上一个请求返回的结果,最终就会一直嵌套下去,令人作呕。

$.ajax({
    //一些请求信息
    success: (data) => {
        $.ajax({
            url: data.src
            //等等
            success: (data) => {
                //some code
            }
        })
    }
})

2.异步产生的错误无法被try catch捕捉到。

try{
    setTimeout(() => {
        console.log(a); //没有定义过的变量a
    }, 1000)
}.catch(error){
    console.log(error); //控制台报错,并不会输出错误信息
}

3.如果一个函数的执行依赖了多个异步返回的结果,难以处理。
大家可以想象一下,加入我要执行函数A,但是它需要10个异步函数返回的结果。怎么处理?可能我们需要定义一个全局变量为一个数组arr,每一个异步函数返回结果后push进去,当数组长度为10时把数组arr作为A的实参去执行A。但是我们又不确定什么时候arr的长度会变成10,总之很麻烦。

第二步:什么是Promise?

Promise汉语直译承诺,其实它是一种解决异步编程问题的模式。他并没消除回调,只是让回调变得更加可控、灵活。

new Promise((res, rej) => {
    res(1)
}).then((data) => {
    console.log(data) //打印 1
})

这就是一个简单的Promise用法,Promise是ES6提供给我们的构造函数,new 返回Promise对象,我们必须传入一个函数作为参数(不然报错),这个函数中可以写入两个参数(也是两个函数,命名随意,通常第一个传resolve,第二个为reject)。
先来说一说Promise的模式:两个阶段,三个状态
image.png
结合上边这一张图和以下代码,

var pro1 = new Promise((resolve, reject) => {
    //未决阶段的一下操作代码
})
console.log(pro1); // 打印Promise{<pending>}

 var pro2 = new Promise((resolve, reject) => {
    resolve('ok');
 })
console.log(pro2); // 打印Promise{<resolved>:"ok"}

 var pro3 = new Promise((resolve, reject) => {
    reject('no');
 })
console.log(pro3); // 打印Promise{<rejected>:"no"}

 var pro4 = new Promise((resolve, reject) => {
    throw('error');
 })
console.log(pro4); // 打印Promise{<rejected>:"error"}

我们发现当我们刚new完的Promise对象处于未决阶段(unsettled)pending状态,当我们执行resolve将会把Promise推向已决阶段(settled)的resolved状态,当我们执行reject将Promise推向已决阶段rejected状态。哪推向不同的阶段由于什么用呢?
其实可以简单理解为Promise进入已决阶段,就要进行一些异步操作了。
接下来,我们就要通过Promise提供的API为他注册一点异步调用的函数。
介绍一下我们用的最多的API,then(),它可以传入两个函数作为参数,如果我们的Promise进入resolved状态(也就是调用resolve函数)就会调用第一个函数,同理,rejected状态就调用第二个函数,函数可以接受一个参数就是我们调用resolve或reject传过来的数据,或者报错信息

new Promise((res, rej) => {
    res(1)
    console.log(2)
    rej(3)
}).then((data) => {
    console.log(data)
},(error) => {
    console.log(error)
})
console.log(4); //打印 2, 4, 1

说到这里有几个需要注意的点,不知道小伙伴们发现没有,
1.未决阶段的代码执行是同步的,会立即执行
我们发现上边代码中2再4之前打印出来,未决阶段没有异步操作,都是同步一行一行执行的。1在最后打印,也就是说我们通过执行res(1)把Promise推向了已决阶段,他接下来所进行的操作都是异步的,要加入微任务队列等待JS执行栈(call stack)为空时再加入栈中执行。这里是一些事件循环的知识了
2.一旦状态推向了已决阶段,无法再对状态做任何更改
上边代码中执行了res(1),Promise将推向resolved状态,再执行rej(3)状态也不会再更改。

第三步:Promise的串联操作

与小伙伴发现,说了半天,虽然说Promise解决了异步操作错误无法捕获的问题,但并没有解决回调地狱。这就要用到Promise的串联了

var pro = new Promise((res, rej) => {
    res("sf")
}).then((data) => {
    //some code
})
console.log(pro); //打印 Promise {<pending>}

看,Promise对象调用then后有返回了一个全新的Promise对象,那么我们是不是就可以一直then一直爽?
我们Promise对象调用then()后会把执行完返回结果,作为全新的Promise内的resolve的参数进行执行,从而又将新Promise推向了已决阶段,然后我们就需要使用then为它注册异步操作。然后...是不是熟悉的味道?

var pro = new Promise((res, rej) => {
    throw("error")
}).then(() => {} ,(data) => {
    return `my${data}`
})
pro.then((data) => {
    console.log(data) //打印 "myerror"
})

你会发现.then()操作之后好像在调用API时,它在内部帮我们做了好多事情。接下让我们探索一下

var pro1 = new Promise((res, rej) => {
    res("here")
})
var pro2 = new Promise((res, rej) => {
    res("there")
})
pro1.then((data) => {
    return pro2
}).then((data) => {
    console.log(data) //打印 "there"
})

这一段代码非常好理解,就是我们帮Promise对象把调用then之后返回一个新Promise对象的事情给做了

var pro = new Promise((res, rej) => {
    res("ok")
})
pro.then().then().then((data) => {
    console.log(data) //打印 "ok"
})

这一段代码我们发现每一次then。内部都

then((data) => {
    return new Promise((res, rej) => {
    res(data);
    })
})

帮我们这样操作了一把。
Promise还有其他一些API,这里就不多结束,道理都差不多


丽塔y
29 声望2 粉丝

hey!