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构造函数接收一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 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
方法可以实现并行执行。
上面这些就是Promise
和then
的基础用法,了解之后我们可以来个小栗子加深印象。
使用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
的用法。
-
我们先用原生js来写一个简单的异步请求,因为所有类似的库基本上都是在原生JS请求的基础上进行封装的
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);
}
}
-
使用Promise来对上面的代码进行简单的封装
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
。
常见使用场景:把异步操作和定时器放到一起,如果定时器先触发,认为超时,告知用户。
网友评论