Promise

97 阅读18分钟

函数对象

函数对象:将函数作为对象使用,就是函数对象

function Person(){

}

Person.age = 18

但是函数对象不能追加Person.name属性,每一个函数对象都有自身的name属性,返回的是函数对象定义的名字:Person.

回调函数的分类

回调函数分为同步、异步。
同步回调:
1.立即在主线程上执行,不会放入回调队列中。比如【数组遍历相关的回调函数】,【Promise的executor函数】。

//同步的回调函数
let arr = [1,3,5,7,9]
arr.forEach((item,index)=>{
    console.log(item)
})
console.log('主线程代码')

异步回调: 1.不会立即执行,会放入回调队列中以后再执行。比如【定时器回调】、【ajax回调】、【Promise的成功和失败的回调】。

//异步的回调
setTimeout(()=>{
    console.log('@')
},0)

console.log('主线程')

就算定时器回调是0秒,他仍然是异步回调,仍然是先执行主线程、再执行队列的。

错误类型说明

错误的类型:
1.Error:所有错误的父类型,如果出错、会提示下方的具体错误类型,而不是Error。
2.ReferenceError:引用的变量不存在;
3.TypeError:数据类型不正确。
4.RangeError:数据值不在所允许的范围内。
5.SyntaxError:语法错误。

回顾捕获错误:catch(..)参数有两个属性:message错误相关信息stack记录信息

try{
    console.log('try中书写可能存在错误的代码')
}catch(error){
    console.log('将try中发生的错误捕获到:',error)
    error.message
    error.stack
}

回顾抛出错误:throw是我们自己知道有错,提示的信息

if(...){

}else{
    //Error()是一个内置对象
    throw new Error('报错啦')
}

初始Promise

Promise是异步编程的新方案(旧方案是--回调函数)。
从语法上来说:Promise是一个内置构造函数
从功能上来说:Promise对象用来封装一个异步操作,并可以获取其成功/失败的值。

  1. Promise不是回调,是一个内置的构造函数,是程序员自己new出来调用的。

  2. new Promise()的时候,要传入一个回调函数,这个回调函数是【同步的】,会立即在主线程上执行,它被称为【executor】函数。

  3. 每一个Promise实例都有3种状态:初始化(pending)成功(fulfilled)失败(rejected)

  4. 每一个Promise实例在刚刚被new出来的那一刻,状态都是初始化(pending)

  5. executor函数会接收到2个参数,他们都是【函数】,分别用形参:resolvereject接收。

5.1 调用【resolve】,会让Promise实例状态变为:成功(fulfilled),同时可以指定成功的value。

5.2 调用【reject】,会让Promise实例状态变为:失败(rejected),同时可以指定失败的reason

//创建一个Promise实例对象
const p = new Promise((resolve,reject)=>{
    resolve('ok')
})

Promise的基本使用

  1. 重要语法: new Promise(executor)构造函数
    Promise.prototype.then方法(原型对象)

  2. 基本编码流程 2.1 创建Promise的实例对象(pending状态),传入executor函数
    2.2 在executor中启动异步任务(定时器、ajax请求)
    2.3 根据异步任务的结果,做不同的处理:
    如果异步任务成功:我们调用resolve(value),让Promise实例对象状态变为成功(fulfilled),同时指定成功的value
    如果异步任务失败:我们调用reject(reason),让Promise实例对象状态变为失败(rejected),同时指定失败的reason。

2.4 通过then方法,为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason。注意:then方法所指定的成功、失败的回调,全部都是【异步的回调】。

关于状态的注意点:

  1. 三个状态: pending:未确定的---初始状态
    fulfilled:成功的---调用resolve()后的状态
    rejected:失败的---调用reject()后的状态

  2. 两种状态的改变 pending---》fulfilled
    rejected---》rejected

  3. 状态只能改变一次!!!(不能既有resolve,又有reject)

  4. 一个promise可以指定多个成功/失败的回调函数(then),

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('成功返回的数据')
    },1000)
})

p.then(
    //回调中可以接收resolve或者reject的参数
    (value)=>{console.log('成功了',value)},//成功的回调--异步
    (reason)=>{console.log('失败了',reason)}//失败的回调--异步
)
p.then(
    //回调中可以接收resolve或者reject的参数
    (value)=>{console.log('成功了',value)},//成功的回调--异步
    (reason)=>{console.log('失败了',reason)}//失败的回调--异步
)

Promise中的resolve和reject返回的是一个值,值交给了p.then中的具体回调函数。

与ajax配合使用

const p = new Promise((resolve,reject)=>{
    const xhr = new XMLHttpRequest()
    
    xhr.onreadystatechange = ()=>{
        if(xhr.readyState === 4){
            if(xhr.status>=200&&xhr.status<300){
                //如果进入到这个判断了,说明响应到了,可以写resolve逻辑
                resolve(xhr.response)
            }else{
                //请求到了,但是状态码不是2xx,说明请求的有点问题
                reject('请求出错了')
            }
        }
    }
    
    xhr.open('GET','https://api.apiopen.top/getJoke')
    xhr.responseType = 'json'
    xhr.send();
    
})

//resolve或者reject后执行的函数
p.then(
    (value)=>{console.log('成功了',value)},
    (reason)=>{console.log('失败了',reason)}
)

封装ajax请求

定义一个sendAjax函数,对xhr的get请求进行封装;
该函数接收两个参数:url(请求地址)、data(参数对象)
该函数返回一个Promise实例:
如果ajax请求成功,则Promise实例成功,成功的value是返回的数据。
如果ajax请求失败,则Promise实例失败,失败的reason是错误提示。

function sendAjax(url,data){
    return new Promise((resolve,reject)=>{
        const xhr = new XMLHttpRequest()
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState===4){
                if(xhr.status>=200&&xhr.status<300){
                    resolve(xhr.response)
                }else{
                    reject('请求出问题')
                }
            }
        }

        //整理参数
        let str = ''
        for(let key in data){
            str += `${key}=${data[key]}&`
        }
        //从最后一个开始,删除1个
        str = str.slice(0,-1)
        
        xhr.open('GET',url)

        xhr.responseType = 'json'

        xhr.send()
    })
}

发送数据:

const x = sendAjax('http://api.apiopen.top',{page:1,count:2,type:'video'})

//x是Promise返回的对象,用来进行判断成功与失败的回调

x.then(
    (value)=>{
        console.log('成功了',value)
    },
    (reason)=>{
        console.log('失败了',reason)
    }
)

Promise_prototype_catch

Promise.prototype.catch:Promise实例.catch(onRejected) 。专门用于:只想要失败的回调,不想要成功的回调。

onRejected:这个方法专门用来传入失败的回调函数(reason)=>{}

说明:catch方法then方法的语法糖(和then的效果一样,只是写法不一样。表达意思相同),等价于:then(undefined,onRejected)第一个不传,但是必须写undefined,占位。

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject(-100)
    },1000)
})

p.then(
    undefined,
    (reason)=>{
        
    }
)
等价于=》
p.catch(
    reason=>{console.log('失败了',reason)}
)

Promise.resolve

Promise:resolve(value):用于快读返回一个状态为fulfilledrejected的Promise实例对象。

value的值可能是:非Promise值、Promise值

这个方法没有在【原型对象】上,所以他是给自己用的,而不是给【实例对象】用的。

const p = new Promise((resolve,reject)=>{
    resolve(100)
})

p.then(
    (value)=>{
        
    },
    (reason)=>{
        
    }
)

如果你就是想输出成功的内容,那么不需要这么麻烦,直接通过Promise.resolve就可以了。

const p = Promise.resolve(100)

p.then(
    (value)=>{
        console.log('成功啦',value)
    },
    (reason)=>{
        
    }
)

Promise.reject

Promise.reject()用于快速返回一个状态为:rejected的Promise实例对象

const p = Promise.reject(900)

p.then(
    value=>console.log('成功了',value)
    reason=>console.log('失败了',reason)
)

Promise.all和Promise.race

Promise.all(promiseArr):其中promiseArr包含n个Promise实例的数组。该方法用来返回一个新的Promise实例,只有所有的Promise都成功了,才成功。只要有一个失败了,就直接失败。

const p1 = Promsie.resolve(a)
const p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('b')
    })
},500)

const p = Promise.all([p1,p2])

p.then(
    value=>console.log('成功啦',value)
    reason=>console.log('失败了',reason)
)

Promise.all中存储的都是成功的Promise实例,返回的是成功Promise实例返回的value,上方返回的是['a','b']

const p1 = Promsie.resolve(a)
const p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('b')
    })
},500)

const p = Promise.all([p1,p2])

p.then(
    value=>console.log('成功啦',value)
    reason=>console.log('失败了',reason)
)

如果失败了,返回的就是具体哪一条失败的回调函数。

Promise.race(promiseArr):返回一个新的Promise实例,成功还是失败?要根据最先出结果的promise为准。

const p1 = Promise.resolve('a')

const p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('b')
    },500)
})

const p = Promise.race([p1,p2])
p.then(
    value=>console.log('成功啦',value)
    reason=>console.log('失败了',reason)
)

返回的一定是p1(成功啦),因为p1最先输出。

可以通过Promise.race([...])数组中的顺序,来动态实现先执行谁,再执行谁。

then方法是可以链式的:

Promise.race([p1,p2]).then(
    value=>...
    reason=>...
)

改变Promise实例状态

如何改变一个Promise实例的状态?
1.执行resolve(value):如果当前是pending就会变为fulfilled。
2.执行reject(reason):如果当前是pending就会变成rejected。
3.执行器函数(executor)抛出异常:如果当前是pending就会变为rejected。

const p = new Promise((resolve,reject)=>{
    //没定义,就直接用,这里是一个异常
    //console.log(a) 引擎抛异常
    
    //throw 900 编码抛异常
})

p.then(
    value=>console.log(value)
    reason=>console.log(reason)
)

此时就会走异常:reason,尽管我没写reject。

改变状态与指定回调的顺序

改变Promise实例的状态 和 指定回调函数 谁先谁后?

  1. 都有可能,正常情况下是【先指定回调、再改变状态】,但是也可以先改变状态再指定回调。
//先指定回调、后改变状态(最常见)
//状态
const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        //这里是指定resolve的回调
        resolve('a')
    },1000)
})

//根据状态回调
p.then{
    value=>
    reason=>
}
  1. 如何【先改状态再指定回调】? 延迟一会儿再调用then()
//先出现成功的状态,然后调用成功的回调
const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('a')
    },1000)
})

setTimeout(()=>{
    p.then(
        value=>
        reason=>
    )
},3000)
  1. Promise实例什么时候才能得到数据? 如果先指定的回调,那么状态发生改变的时候,回调函数就会调用,得到数据。
    如果先改变的状态,那么当指定回调时,回调函数就会调用, 得到数据。

then的链式调用

Promise实例.then()返回的是一个【新的Promise实例】,他的值和状态由什么决定?
简单表达:由then()所指定的回调函数执行的结果,来决定。
详细表达:

  1. 如果then所指定的回调,返回的是非Promise值a 那么【新Promise实例】状态为:成功(fulfilled),成功的value是a。
  2. 如果then所指定的回调,返回的是一个Promise实例p 那么【新Promise实例】的状态、值,都与p一致。
  3. 如果then所指定的回调抛出异常: 那么【新Promise实例】状态为rejected,reason为抛出的那个异常。
const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('a')
    },1000)
})

const x = p.then(
    value=>{console.log('成功啦1',value)},
    reason=>{console.log('失败啦1',reason)}
)
x.then(
    value=>{console.log('成功啦2',value)}
    reason=>{console.log('失败啦2',reason)}
)

最后的输出结果:成功啦1,a;成功啦2,undefined
因为then指定的回调,返回的是回调的return值,在成功1那里只是console.log,没有return,所以返回值是undefined,因此x的值就是undefined。

如果then的返回值:是一个【非Promise】值,那么返回的结果就是true,就走成功的调用。

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('a')
    },1000)
})

p.then(
    value=>{console.log('成功啦1',value)},
    reason=>{console.log('失败啦1',reason)}
).then(
    value=>{console.log('成功啦2',value)}
    reason=>{console.log('失败啦2',reason)}
)

因为reject,所以走失败啦1,又因为失败啦1,a语句的返回值是undefined,程序员没有手动return,是一个【非Promise值】因此表示true,后面的then走的就是true的路线,输出成功啦2,undefined

上方这种写法就是then的链式调用。

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('a')
    },1000)
})

p.then(
    value=>{console.log('成功啦1',value);return Promise.reject('a')},
    reason=>{console.log('失败啦1',reason)}
).then(
    value=>{console.log('成功啦2',value);return true},
    reason=>{console.log('失败啦2',reason);return 100}
).then(
    value=>{console.log('成功啦3',value);return false},
    reason=>{console.log('失败啦3',reason);return false}
).then(
    value=>{console.log('成功啦4',value);return -100},
    reason=>{console.log('失败啦4',reason)}
)
成功了1a
失败了2a
成功了3,100
成功了4,false

如果抛了异常,那么走的【失败的路线】,并且将异常的值作为失败路线的值。

p.then(
    value=>{console.log('成功啦1',value);return Promise.reject('a')},
    reason=>{console.log('失败啦1',reason)}
).then(
    value=>{console.log('成功啦2',value);return true},
    reason=>{console.log('失败啦2',reason);return 100}
).then(
    value=>{console.log('成功啦3',value);return false},
    reason=>{console.log('失败啦3',reason);throw 900}
).then(
    value=>{console.log('成功啦4',value);return -100},
    reason=>{console.log('失败啦4',reason)}
)
成功了1a
失败了2a
成功了3100
失败啦4,900

总结:如果返回值是一个【非Promise】值,那么走的一定是resolve的路线,如果返回值是一个【Promise】值,根据具体的返回结果判断走resolve回调还是reject的回调。

纯回调引发的回调地狱

存在异步任务的代码,不能保证能按照顺序执行,那如果我们非要代码顺序执行呢?

 setTimeout(function () {  //第一层
    console.log('武林要以和为贵');
    setTimeout(function () {  //第二程
        console.log('要讲武德');
        setTimeout(function () {   //第三层
            console.log('不要搞窝里斗');
        }, 1000)
    }, 2000)
}, 3000)

需要将第二次的请求,放在第一次的请求里面,这样才能保证顺序执行正确。

可以看到,代码中的回调函数套回调函数,居然套了3层,这种回调函数中嵌套回调函数的情况就叫做回调地狱。

总结:回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。

解决回调地狱

学习Promise的目的就行可以用来封装一个异步操作,并且可以获取成功和失败的值。

function sendAjax(url,data){
    return new Promise((resolve,reject)=>{
        const xhr = new XMLHttpRequest
        
        xhr.onreadystatechange = ()=>{
            if(xhr.readyState === 4){
                if(xhr.status>=200&&xhr.status<300){
                    resolve(xhr.response)
                }else{
                    reject('出错了')
                }
            }
        }
        
        //整理参数
        let str = ''
        for(let key in data){
            str += `${key}=${data[key]}&`
        }
        str = str.slice(0,-1)
        
        xhr.open('GET',url+'?'+str)
        xhr.responseType = 'json'
        xhr.send()
    })
}

//发送第1次请求
sendAjax('url地址',{page:1,count:2}).then(
    value=>{
        console.log('第1次请求成功',value);
        
        //发送第2次请求
        return sendAjax('url地址',{page:1})
        
    },
    reason=>{
        console.log('第1次请求失败',reason)
    }
).then(
    value=>{
        console.log('第2次请求成功',value)
        
        //发送第3次请求
        return sendAjax('url地址',{page:1})
        
    },
    reason=>{
        console.log('第2次请求失败',reason)
    }
).then(
    value=>{
        console.log('第3次请求成功',value)
    },
    reason=>{
        console.log('第3次请求失败',reason)
    }

)

中断Promise链

image.png

如果不中断Promise链,就会按照Promise的原则进行输出:
第一次请求出错,就会调用reason,因为reason的返回值没写,就是undefined,会直接走下一次正确的回调,所以第二次then会执行正确的回调,但是因为没有返回值,所以value输出undefined。因为第二次的请求是正确的,所以会执行第三次then,第三次正确会执行他的value,并且因为第二次是正确的,有返回值,所以只有第三次的返回内容是最全面的。

我们不希望看到上面的情况,如果我第一次发送失败了,我第二次的发送,我就不想让他发送了,所以需要中断Promise链。

.then(
    value=>{...},
    reason=>{
        console.log('第1次请求失败了',reason);
        //中断Promise链
        return new Promise(()=>{})
    }
).then(
    value=>{
        console.log('第2次请求不到了',reason);
    },
    reason=>{
        console.log('第2次请求不到了',reason);
    }
)

此时如果我第一次请求失败,我会走reason,通过失败回调函数中的return new Promise(()=>{})来中断Promise链,因为这句话既不算成功、也不算失败,【他是一个初始化pendding状态的Promise实例】,所以就中断了,第2次的then就不会执行到啦。

总结:当使用Promise的then链式调用时,在中间中断,不再调用后面的回调函数,就可以在【失败的回调函数中】返回一个pendding状态的Promise实例。

错误的穿透

Promise的错误穿透:
1.当使用promise的then链式调用时,可以在最后,用catch指定一个失败的回调。
2.前面任何操作,一旦出了问题,都会传入到最后失败的回调中处理。不过不存在then的链式调用,就不需要考虑then的错误穿透。

我们再也不用写N多个失败的回调啦,直接写一个catch就完事了!!!这就是错误的穿透!!!

sendAjax('url地址',{page:1}).then(
    value=>{}
).then(
    value=>{}
).then(
    value=>{}
).catch(
    reason=>{
        console.log('失败了',reason)
    }
)

image.png

底层是给每个失败补了一句:reason=>{throw reason},不处理这个错误,一直往外抛、但是到了最后一句catch就可以捕获到,最终输出错误。

Promise的优势

  1. 指定回调函数的方式更加灵活 旧的写法:必须在启动异步任务前指定
    Promise:启动异步任务、返回Promise对象、给Promise对象绑定回调函数(甚至可以在异步任务结束后指定)

  2. 支持链式调用,可以解决回调地狱的问题 回调地狱:回调函数嵌套使用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件,使得代码不便于阅读、不便于异常的处理。

但是then的链式调用仍然不是一个优秀的解决方案,终极解决方案:async/await(底层实际上依然用的是then的链式调用)

async和await

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('a')
    },1000)
})

p.then(
    value =>{console.log('成功了',value)}
    reason =>{console.log('失败了',reason)}
)

如果我只想拿到成功的值,就可以借助await,await:只能用来获取成功的值。

前提:await必须和async函数搭配使用

async function demo(){
    const result = await p
    console.log(result)//a
}
demo()

如果reject('a'),那么await永远也获取不到。

如何获取错误的值?

const p = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('a')
    },1000)
})

async function demo(){
    try{
        const result = await p
        console.log(result)
    }catch(error){
        console.log(error)//a
    }
}

通过try..catch(error)中的error来获取失败的值。

简写:立即执行函数前面最好写一个; 或者 !

const p1 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('a')
    },1000)
})

const p2 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve('b')
    },2000)
})

const p3 = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        reject('b')
    },2000)
})

;(async()=>{
    try{
        const result1 = await p1
        console.log(result1)
        
        const result2 = await p2
        console.log(result2)
        
        const result3 = await p3
        console.log(result3)
                
    }catch(error){
        console.log(error)
    }
    
})()

try中一旦出现了错误,那么剩下的代码就不会执行!

await的应用

;(aysnc()=>{
    try{
        const result1 = await sendAjax('url地址',{age:1});
        console.log('第一次请求成功',result1)

        const result2 = await sendAjax('url地址',{age:2});
        console.log('第二次请求成功',result2)

        const result3 = await sendAjax('url地址',{age:3});
        console.log('第三次请求成功',result3)
    }catch(error){
        console.log(error);
    }
})()

如果1出现了问题,那么在try-catch中,1后面的所有代码都不会执行。

await和async的规则
  1. async修饰的函数: 函数的返回值为promise对象
    Promise实例的结果由async函数执行的返回值决定

  2. await表达式: await右侧的表达式一般为Promise实例对象,但也可以是其他的值
    如果表达式是promise对象,await后的返回值是promise成功的值
    如果表达式是其他值,直接将此值作为await的返回值

  3. 注意: await必须写在async函数中,但async函数中可以没有await
    如果await的promise失败了,就会抛出异常,需要通过try...catch来捕获处理

async function demo(){
    const result = await p
    console.log(result)
    console.log(100)
    console.log(200)
}
demo()
console.log(1)

实际上先输出了最后的1,再输出了result、100、200.

因为await的原理:本质上还是一个then回调函数

async function demo(){
    //const result = await p
    p.then(
        result=>{
            console.log(result)
            console.log(100)
            console.log(200)
        }
    )
}

宏任务与微任务

宏任务与微任务,又可以说是:宏队列与微队列。

队列可以分为【宏队列】和【微队列】。【宏队列】中有宏任务,【微队列】中有微任务

定时器是宏任务,放在宏队列中。

Promise的回调是微任务,放在微任务中。

在队列中,先执行【微队列】,再执行【宏队列】。

setTimeout(()=>{
    console.log('timeout')
})

Promise.resolve(1).then(
    value => console.log('成功1')
)

Promise.resolve(2).then(
    value => console.log('成功2')
)

输出:成功1、成功2、timeout;先执行微队列,再执行宏队列。

微任务队列:有Promise的then回调、MutationObserver的API(很少用);

宏任务队列:定时器回调、dom事件的回调、ajax事件的回调;

在执行任何的宏任务之前,都要保证微任务队列被清空。如果微任务队列不为空,那么就优先执行微任务队列中的任务(回调)

面试题

  1. 第一题
setTimeout(function () {
  console.log("setTimeout1");
  new Promise(function (resolve) {
    resolve();
  }).then(function () {
    new Promise(function (resolve) {
      resolve();
    }).then(function () {
      console.log("then4");
    });
    console.log("then2");
  });
});

new Promise(function (resolve) {
  console.log("promise1");
  resolve();
}).then(function () {
  console.log("then1");
});

setTimeout(function () {
  console.log("setTimeout2");
});

console.log(2);

queueMicrotask(() => {
  console.log("queueMicrotask1")
});

new Promise(function (resolve) {
  resolve();
}).then(function () {
  console.log("then3");
});

解析:

  • 定时器一上来就直接被包裹在宏任务中。
  • Promise的reslove或reject直接放在主线程执行,不会放在微任务/宏任务中,但是Promise.then回调是要放在微任务的。
  • 回调函数放在微任务中

第一次的过程:

  • 主线程:promise1,2
  • 微任务:then1,queueMicrotask1,then3
  • 宏任务:setTimeout1,setTimeout2

第二次的过程:

  • 将微任务中的所有任务推送到主线程中
  • 主线程:promise1,2,then1,queueMicrotask1,then3
  • 宏任务:setTimeout1,setTimeout2

第三次的执行过程:

  • 执行第一个setTimeout,一上来就是一个console.log,所以会直接放在主线程:setTimeout1。

  • 第二句Promise也是一个立即执行的,不会放在任务中,但是resolve有一个回调,所以后面的一大段全部放在微任务中。new Promise...

  • 因为先执行微任务的,所以后面的setTimeout2又不能被执行

  • new Promise将then4加入微任务,console.log的then2会直接放在主线程中

    • 主线程:promise1,2,then1,queueMicrotask1,then3,setTimeout1,then2,then4,setTimeout2

补充:

async function bar() {
  console.log("22222")
  return new Promise((resolve) => {
    resolve()
  })
}

async function foo() {
  console.log("111111")

  await bar()

  console.log("33333")
}

foo()
console.log("444444")
  • 默认是不会执行函数的,只有调用的时候才会。
  • 因为foo是一个异步的,但是调用异步函数,实际上是会同步执行的,谁调用你,你就会直接执行。
  • 只要属于同一个代码块,就会按照代码块顺序执行。
  1. 第二题
async function async1() {
  console.log('async1 start')
  await async2();
  console.log('async1 end')
}

async function async2() {
  console.log('async2')
}

console.log('script start')

setTimeout(function () {
  console.log('setTimeout')
}, 0)

async1();

new Promise(function (resolve) {
  console.log('promise1')
  resolve();
}).then(function () {
  console.log('promise2')
})

console.log('script end')
  • 前两个函数没有被调用,就不会被执行,来到console,先执行console.log,直接放在主线程:script start

  • setTimeout是宏任务,直接丢宏任务里

  • async1,调用第一个函数,不管函数是同步还是异步,只要被调用,就是立马塞进主线程里的,直接输出async1 start,然后语句块调用async2,输出async2

  • 因为用的是await,相当于new Promise.then,后面输出的async1 end没有返回值,因此Promise是undefined,会丢到微任务中。

    • 此时主线程:script、async1、async2,微任务有:async1 end;宏任务:setTimeout
  • 执行下面的new Promise,直接将promise1输出主线程,然后将后面的then中的promise2放入微任务中。

  • scrpit end直接输出

    • 主线程:script、async1、async2、promise1、end;微任务:async1 end、promise2,宏任务:setTimeout

最后的输出结果:

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
  1. 第三题
Promise.resolve().then(() => {
  console.log(0);
  return Promise.resolve(4)
}).then((res) => {
  console.log(res)
})

Promise.resolve().then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
}).then(() => {
  console.log(3);
}).then(() => {
  console.log(5);
}).then(() =>{
  console.log(6);
})