Promise
在了解Promise之前,我们来看一段代码
function foo(){
setTimeout(()=>{
console.log('1')
},1000)
}
function bar(){
console.log('2')
}
foo()
bar
看完这段代码之后,按照传统的,代码从上往下执行,很多人会以为先打印出1,然后再打印出2,但是事实真是这样吗?
事实上,结果恰恰相反,事实上,结果是先打印2,后打印1
我们要知道,js,是单线程执行的,当在调用一个需要过一段时间才执行(像定时器,ajax)的函数时, 会先挂在那里不执行,先执行不耗时间的函数。
上面那段代码,实际上,时先调用bar(),然后再调用foo(),所以是先打印2,后打印1
那么,有没有一种办法,先执行foo(),后执行bar()呢?
答案是肯定的,下面我们来介绍一下Promise
Promise简单介绍
Promise是一个构造函数,用来生成Promise实例。实例代表一个异步操作的最终完成(或失败)及其结果值。
- Promise有三种状态:
Resolved(已完成) :成功状态,表示操作成功完成。
Rejected(已失败) :失败状态,表示操作失败。
Pending(等待中) :初始状态,既没有被成功(resolved)也没有被拒绝(rejected)。
那么,怎么用Promise来解决上面那个问题呢?
再这里,我们用到的是return new Promise((resolve, reject) => { ... })
return new Promise((resolve, reject) => { ... }) 是JavaScript用来启动一个异步任务并处理其结果的方式。
-
Promise是一个容器,它会容纳未来可能产生的结果(成功或失败)。 -
当你创建一个
Promise,你需要给它一个函数(我们称之为 executor 函数),这个函数有两个参数:resolve是一个函数,当你想说“任务成功了,这里是结果”时,就调用它。reject也是个函数,如果任务出错了,就调用它来报告错误。
function a(){
return new Promise((resolve,reject)=>{
setTimeout(() => {
console.log(1) //怎么让1先执行呢
resolve('xq sucess')
}, 2000)
})
}
function marry(){
return new Promise((resolve, reject)=>{
setTimeout(() => {
console.log(2)
}, 1000)
})
}
a()
.then((res1)=>{
console.log(res1)
return marry()
函数a() :模拟一个耗时2秒的异步操作,
函数marry() :模拟一个耗时1秒的异步操作,
看一下执行的结果
结果是我们想要的:1在2之前打印
为什么是这样的顺序?
-
1.首先,
a()函数被调用,它返回一个Promise并在2秒后打印数字1,随后调用resolve('xq success'),将Promise状态变为fulfilled。 -
2.这时,
a()的.then被触发,打印出a()解决时传递的字符串'xq success'。 -
3.接下来,在
.then中调用marry()函数,它同样返回一个Promise并在1秒后打印数字2。但是,请注意,在marry()的Promise中,虽然执行了resolve函数来改变Promise状态为fulfilled,但实际上并没有提供任何参数给resolve函数(根据您的代码显示),因此在marry()的链式调用中没有额外的值被打印出来。
打印结果将是数字1,字符串'xq success',然后是数字2
问题来了,.then后面的参数res1是指什么
res1就是a()中resolve函数传递的参数,即字符串'xq success'
下面来看一段复杂的代码
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1) //怎么让1先执行呢
resolve('xq sucess')
}, 2000)
})
}
function marry() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(2)
resolve('happy')
}, 1000)
})
}
function baby() {
}
a()
.then((res1) => {
console.log(res1)
return marry()
}).then((res2) => {
console.log(res2)
baby()
})
相信看见这段代码,按照之前的解释,你应该知道最后输出的顺序了吧!
1
a()函数在2秒后打印1,并使用resolve方法将Promise状态改为fulfilled,传递字符串'xq success'。
2 这导致第一个.then中的回调函数执行,打印res1,即'xq success',然后返回marry()函数的Promise。
3 marry()函数的Promise在1秒后打印2,并使用resolve方法传递字符串'happy',使得它的Promise变为fulfilled。
4 这又触发了紧跟的第二个.then,打印出res2,即'happy',并调用baby()函数。
- 由于
baby()函数体内没有任何实现,所以它被执行但没有额外的输出或可见效果。 因此,整体的输出顺序为:1->'xq success'->2->'happy',然后baby()被调用但无输出。
问题来了,第二个.then后面的参数res2是指什么
第二个.then后面的参数res2是指由前一个Promise(即marry()函数返回的Promise)通过resolve方法传递的值,即字符串'happy'
下面,我们来讲一下js当中,fectch是如何通过Promise将ajax进行封装的
function getData() {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mock.mengxuegu.com/mock/65a91543c4cd67421b34c898/movie/movieList', true);
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
let movieList = JSON.parse(xhr.responseText).movieList;
console.log(movieList)
//console.log(xhr.responseText);
resolve(movieList)
}
}
}
function renderLi(arr) {
//创建li
arr.forEach(item => {
let li = document.createElement('li')
li.innerHTML = item.nm
document.getElementById('ul').appendChild(li);
});
}
document.getElementById('btn').addEventListener('click', () => {
getData().then(res => {
renderLi(res)
})
})
这些是之前我们通过ajax,向后端发送请求,获取电影列表的代码
在这里getData()叫做异步代码(需要消耗时),renderLi叫做同步代码,我们必须异步拿到数据之后,再执行同步代码
所以我们需要在getData()函数里return new Promise((resolve, reject) => { ... })
function getData() {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', 'https://mock.mengxuegu.com/mock/65a91543c4cd67421b34c898/movie/movieList', true);
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
let movieList = JSON.parse(xhr.responseText).movieList;
console.log(movieList)
//console.log(xhr.responseText);
resolve(movieList)
}
}
})
}
function renderLi(arr) {
//创建li
arr.forEach(item => {
let li = document.createElement('li')
li.innerHTML = item.nm
document.getElementById('ul').appendChild(li);
});
}
document.getElementById('btn').addEventListener('click', () => {
getData().then(res => {
renderLi(res)
})
})
这样,我们在执行renderLi函数之前,执行了getData()
在这里,你发现了什么?
我们是不是可以把url地址变成getData()的一个参数,是不是就完成了一个封装的操作
function getData(url) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
let movieList = JSON.parse(xhr.responseText).movieList;
console.log(movieList)
//console.log(xhr.responseText);
resolve(movieList)
}
}
})
function renderLi(arr) {
//创建li
arr.forEach(item => {
let li = document.createElement('li')
li.innerHTML = item.nm
document.getElementById('ul').appendChild(li);
});
}
document.getElementById('btn').addEventListener('click', () => {
getData('https://mock.mengxuegu.com/mock/65a91543c4cd67421b34c898/movie/movieList')
.then(res => {
return res.JSON()
}).then(data =>{
console.log(data)
})
})
}
观察一下,这里的getData()是不是就是fetch
此时此刻的你
原来fetch是这么来的,这下恍然大悟了
那么Promise里reject是什么呢
reject是用来捕获错误,当在调用一个需要过一段时间才执行(像定时器,ajax)的函数时,像发送请求可能失败,这时候需要.catch
总结一下
1.then((res) =>{}) res是promise中的resolve(xx)出来的值
2.catch((res) =>{}) err是promise中的reject(xx)出来的值