Promise基础详解

2020-08-06  本文已影响0人  橙色流年
Promise的含义

Promise是异步编程的一种解决方案,ES6将其写进了语言标准。所谓的Promise就是一个容器,里面保存着未来才会结束的事件(通常是一个异步操作)的结果。

Promise对象有以下两个特点
  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态: pending(进行中)fulfilled/resolve(已成功)reject(已失败)。只有异步操作的结果,可以决定当前是哪种状态,任何其他操作都无法改变这个状态。
  • 一旦状态改变,就不会再改变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled或者从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
Promise的优点
  • 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
Promise的基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

const promise = new Promise((resolve, reject) => {
    // 此处可以用来处理执行一些异步操作
    if ("异步操作执行成功") {
        resolve(value)
    } else {
        reject(error)
    }
})
// 其中两个函数的参数值分别为成功失败后想要传递的结果。

Promise构造函数接收一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

then方法可以接收两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接收Promise对象传出的值作为参数。

promise.then(res => {
    // 对成功回调接收的数据进行处理
}, error => {
    // 对于失败的回调数据进行处理  
})

结合上面的总结,我们可以先来看一个简单的小栗子帮助我们初步了解Promise的基本用法

const promise = new Promise((resolve, reject) => {
    console.log(111)
    resolve(333)
    console.log(222)
})
promise.then(res => {
    console.log(res)
})
console.log(444)

上面的代码中,你认为的打印顺序会是怎样?实际输出结果如下

111
222
444
333

所以这里我们不要理解混了,认为new Promise()这个构造函数里面的都是异步的内容,实际上Promise新建以后会立即执行。前面我们说过resolve()的作用是,将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。所有这里resolve(333)说明这里会执行异步操作,并且成功后将333的值传递到then的参数中让其接收。

了解Promise.prototype.then()
  • Promise实例具有then方法,也就是说,then方法是定义在原型对象上Promise.prototype上的,它的作用是为 Promise实例添加状态改变时的回调函数。前面说过,then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
  • then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面在调用另一个then方法。第一个回调函数完成以后会将结果作为参数传入第二个回调函数。
const promise = new Promise((resolve, reject) => {
    resolve(111)
})
promise.then(res => {
    console.log(res) // 111
    return res+=111
}).then(res => {
    console.log(res) // 222
    return res+=111
}).then(res => {
    console.log(res) // 333
})

上面的代码中,我们用了then链式调用,结合前面说的,then返回的是一个新的Promise实例,并且会把前一个函数的返回结果作为参数传入到当前函数。

划重点,then方法在链式调用中下一个then方法如果要得到当前then方法的结果,必须在当前方法中return将结果返回出去才能被接收到,所以链式调用一定不要将return忘了。

因此采用链式的 then,可以指定一组按照次序调用的回调函数。(ES7中的async/await)也可以实现链式调用,除此之外,Promise的all方法可以实现并行执行。

上面这些就是Promisethen的基础用法,了解之后我们可以来个小栗子加深印象。

使用Promise来模拟红绿灯功能

项目需求:初始亮红灯,红灯亮9s之后亮绿灯,绿灯亮6s之后亮黄灯,黄灯亮3s之后在亮红灯,实现多次交替亮灯效果;并希望亮灯交换的过程可以以倒计时的方式来提醒用户。

栗子比较简单,小伙伴们可以自己思考并进行实现,下面是我自己的实现方式:

let lightArr = [
    {
        color: 'red',
        second: 9,
        default: true
    },
    {
        color: 'green',
        second: 6,
        default: false
    },
    {
        color: 'yellow',
        second: 3,
        default: false
    }
]

function light(color, time) {
    return new Promise((resolve, reject) => {
        let t  = setInterval(() => {
            console.log('当前亮灯情况为:' + color + ',剩余亮灯时间:' + time + 's')
            time-=1
            if (time === 0) {
                clearInterval(t)
            }
        }, 1000)
        setTimeout(() => {
            resolve()
        }, time * 1000)
    })
}

function orderList(list) {
    let promise = Promise.resolve()
    list.forEach((item) => {
        promise = promise.then(() => {
            return light(item.color, item.second)
        })
    })
    
    promise.then(() => {
        return orderList(list)
    })
}

orderList(lightArr)

可以看到我在lightArr数组中也定义了一个default属性,这里其实我想实现的就是可以任意改变红绿黄灯的初始值,如果我们将初始值改成先亮绿灯,那么亮灯顺序就是绿灯之后黄灯再之后红灯循环。喜欢钻研的小伙伴可以琢磨一下,实现方法其实也很简单,一个小小的案例可以通过我们不断的钻研去让它变得更有趣。也能让我们自己学到更多的东西。

使用Promise来简单封装一个异步请求

其实我们用的大部分异步请求库基本都是用Promise来进行封装的,在这里我们自己也可以封装一个简单的异步请求来让我们更了解这个过程和Promise的用法。

let url = "http://fzjt.weasing.com/index.php/v1/articlelist/banner"
let XHR = new XMLHttpRequest()
XHR.open('GET', url, true)
XHR.send()
XHR.onreadystatechange = function() {
    console.log(XHR)
    if (XHR.readyState == 4 && XHR.status == 200) {
        let result = JSON.parse(XHR.response);
        console.log(result);
    }
}
function getJson(type, url) {
    return new Promise((resolve, reject) => {
        let XHR = new XMLHttpRequest()
        XHR.open(type, url, true)
        XHR.send()
        XHR.onreadystatechange = function() {
            if (XHR.readyState === 4) {
                if (XHR.status === 200) {
                    try {
                        let response = JSON.parse(XHR.responseText)
                        resolve(response)
                    } catch (e) {
                        reject(e)
                    }
                } else {
                    reject(new Error(XHR.statusText))
                }
            }
        } 
    })
}
// 调用getJson
getJson('GET', url1).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})
getJson('POST', url2).then(res => {
    console.log(res)
}, err => {
    console.log(err)
})

可以简单看下上面的代码,似乎第二种比第一种方法更麻烦,但是实则不然。第二种我们处理了多种情况,使得代码的扩展性更好,并且第二种我们如果有很多个请求就可以直接通过最下面的调用方式来调用,省时省力。当然实际开发中代码的封装肯定要包含更多种情况,请求头验证啥的肯定也要加上,这里只是跟大家分享一种思维来更好的理解Promise

Promise.all的用法

当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。

Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。

function renderAll () {
   // 接收一个Promise对象组成的数组当做参数
    return Promise.all([getJson('GET', url1), getJson('POST', url2)])
}

renderAll().then(res => {
  // 返回一个数组,数组对应的值为两个返回函数的值[{...},{...}]
    console.log(res) 
})

再看一个小栗子:

let wake = (time) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(`${time / 1000}秒后醒来`)
        }, time)
    })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
    console.log(result) // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
    console.log(error)
})

上面栗子中先输出的是p1,然后才是p2。可是我们在代码中明明让p2的倒计时设置为2s比p1快1s。但是仍然是按照调用的顺序来执行的。所以我们这里得出结论:

Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组参数顺序是一致的。

这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

关于Promise.all()的使用得到如下总结:
  • 它接受一个数组作为参数
  • 数组可以是Promise对象,也可以是其它值,当全部是Promise时会等待状态改变。
  • 如果有任何一个失败,该Promise失败,返回值是第一个失败的子Promise的结果。
  • Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的
Promise.race()的用法

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

看下面这个小栗子:

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    },1000)
})

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('failed')
    }, 500)
})

Promise.race([p1, p2]).then((result) => {
    console.log(result)
}).catch((error) => {
    console.log(error)  // 打开的是 'failed'
})

p2的执行时间是500ms之后先于p1,所有使用race()方法之后会执行p2的方法而忽略掉p1

常见使用场景:把异步操作和定时器放到一起,如果定时器先触发,认为超时,告知用户。

上一篇 下一篇

猜你喜欢

热点阅读