面试官叫我手搓一个fetch,还给了我一个承诺(Promise)
前言
面试官:同学手搓一个fetch吧
我:啊?不是直接用的嘛?
面试官:给你一个Promise拿去用。
我:这两个怎么扯上关系的?
Promise就像一张承诺的便条,它代表着未来某个时刻会给你带来结果的承诺。你可以在它上面写下你的期望,然后去做其他事情。当它实现了承诺,你就可以拿到结果。而如果承诺失败了,你可以捡起便条,看看是哪里出了问题。Promise让你的代码更加优雅、可读,同时也更容易处理异步操作。
前景引入
function foo(){
setTimeout(() => {
console.log('艾总');
}, 1000);
}
function bar() {
console.log('盛盛');
}
foo()
bar()
针对这段代码,我们会发现先打印盛盛,不是foo先执行嘛?
首先我们要知道js是单线程的一次只能执行一件事情。在JavaScript中,同步操作是按照代码顺序依次执行的方式,每一行代码都要等待上一行代码执行完毕才能继续执行。而异步操作是指不需要等待上一行代码执行完毕就能继续执行的操作。正因为有了异步,才让js的执行变得流畅,不然比如有一段代码需要运行一个小时,这个时候我们就需要异步处理,不然它后面的所有代码都要等它执行完毕才能执行,这是不可取的。
早期处理方式
let data = {}
function a() {
setTimeout(() => {
data = { name: '艾总' }
b()
}, 1000)
}
function b() {
console.log(data.name + '好帅');
}
a()
那如果我们硬要前面的代码先执行,我们可以怎么做呢。js的程序员们早期采用嵌套的方式,我们可以发现,这样的话必须执行完了a内的逻辑才会执行b,但是我们设想,如果是多重嵌套呢?比如嵌套了a的执行依赖b,b依赖c,c依赖d,d依赖e,如果一旦e有变动了,会影响很多依赖相关数据,因此这种方式是有弊端的。
Promise
function xq() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('艾总相亲了');
resolve('艾总他要去相亲了喔')
}, 2000)
})
}
function marry() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('艾总结婚了');
resolve('祝我们艾总新婚快乐了喔')
}, 1000);
})
}
function baby() {
console.log('小艾出生了');
}
xq()
.then((res) => {
console.log(res);
marry().then((res1) => {
console.log(res1);
baby()
})
})
function xq() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('艾总相亲了');
resolve('艾总他要去相亲了喔')
}, 2000)
})
}
function marry() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('艾总结婚了');
resolve('祝我们艾总新婚快乐了喔')
}, 1000);
})
}
function baby() {
console.log('小艾出生了');
}
xq()
.then((res) => {
console.log(res);
return marry()
})
.then((res1) => {
console.log(res1);
baby()
})
js官方打造了promise用来专门处理需要时间执行的任务优于不需要时间执行的任务先执行(这里的时间是相对的),把异步变成了我们想要的‘同步’。我们在需要先执行的函数里面放入return new Promise里面接一个回调函数,有两个参数resolve,reject,我们先做成功resolve的讲解。首先resolve里的参数会被之后的then接收。
在我们Promise了需要先执行的函数之后,采用then的方式执行下一个需要执行的代码,其中resolve传参给then里的形参。如果要让里面的函数又先于后一个函数先执行,我们可以用第一种方法,也可以用第二种,第二种会更优雅一些。
另一种参数
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a is ok');
reject('a error')
}, 1000)
})
}
function b() {
console.log('b is ok');
}
a().then(() => {
b()
})
.catch((err) => {
console.log(err);
})
其实Promise里还有一个代表失败的参数reject,如果我们采用reject,那它后面的逻辑都不会被执行了这个时候采用catch进行捕获这个参数。也就是说,then用来进行成功操作,catch用来进行失败操作。
Promise里的方法
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('a is ok');
resolve('a error')
}, 1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('b is ok');
resolve('b error')
}, 500)
})
}
function c() {
console.log('c is ok');
}
// Promise.race([a(), b()]).then(() => {
// c()
// })
Promise.all([a(), b()]).then(() => {
c()
})
位于Promise里还有一些好用的方法,上面分别对应两次打印情况,race方法中只要有一个promise对象出来后立马调用后面then内容执行,而all方法必须等里面所有内容执行完毕之后才会执行then的内容。
看看fetch的底层?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">请求数据</button>
<ul id="ul"></ul>
<script>
function getData() {
return new Promise((resolve) => {
let xhr = new XMLHttpRequest()
xhr.open('Get', 后端地址, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status === 200) {
// console.log(xhr.responseText);
let movelist = JSON.parse(xhr.responseText).movieList
// console.log(movelist);
resolve(movelist)
}
}
})
}
function renderLi(arr) {
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)
})
})
</script>
</body>
</html>
这里我们搞了一个简单的html结构,然后定义了一个获取接口信息的函数以resolve返回信息。然后将其中的信息渲染到html里去。最后我们通过then来接收这个信息。怎么样是不是有点熟悉了?如果还没反应过来的话,看看这里
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">请求数据</button>
<ul id="ul"></ul>
<script>
function getData(url) {
return new Promise((resolve) => {
let xhr = new XMLHttpRequest()
xhr.open('Get', url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status === 200) {
// console.log(xhr.responseText);
let movelist = JSON.parse(xhr.responseText).movieList
// console.log(movelist);
resolve(movelist)
}
}
})
}
function renderLi(arr) {
arr.forEach(item => {
let li = document.createElement('li')
li.innerHTML = item.nm
document.getElementById('ul').appendChild(li)
})
}
document.getElementById('btn').addEventListener('click', () => {
getData(url).then((res) => {
renderLi(res)
})
})
</script>
</body>
</html>
这个时候我们就会发现getData这个函数和fetch的作用是一样的!(除了最后转换json略微不同)
因此我们相当于成功手搓了一个fetch,本质上是由Promise和原生ajax封装而成的。
转载自:https://juejin.cn/post/7374631918112079910