面试常考:Promise的用法,以及fetch的原理

485 阅读5分钟

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秒的异步操作,

看一下执行的结果

微信图片_20240530143018.png

结果是我们想要的:12之前打印

为什么是这样的顺序?

  • 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()
            })

相信看见这段代码,按照之前的解释,你应该知道最后输出的顺序了吧!

微信截图_20240530145237.png 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()的一个参数,是不是就完成了一个封装的操作

微信截图_20240530152334.png

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

此时此刻的你

微信截图_20240530153215.png

原来fetch是这么来的,这下恍然大悟了

那么Promise里reject是什么呢

reject是用来捕获错误,当在调用一个需要过一段时间才执行(像定时器,ajax)的函数时,像发送请求可能失败,这时候需要.catch

总结一下

1.then((res) =>{}) res是promise中的resolve(xx)出来的值

2.catch((res) =>{}) err是promise中的reject(xx)出来的值

制作不易,感谢支持

image.png