在了解Promise之前,我们来看一段代码
js
复制代码
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
也是个函数,如果任务出错了,就调用它来报告错误。
js
复制代码
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'
下面来看一段复杂的代码
js
复制代码
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进行封装的
js
复制代码
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) => { ... })
js
复制代码
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()的一个参数,是不是就完成了一个封装的操作
js
复制代码
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)出来的值
原文链接:https://juejin.cn/post/7374661722466123830