3.1、前端异步编程(超详细手写实现Promise;实现all、race、allSettled、any;async/await的使用)
前端异步编程规范
- Promise介绍
- 手写Promise(resolve,reject)
- 手写Promise(then)
- Promise相关 API实现
- all
- race
- allSettled
- any
- async/await和Promise的关系
- async/await的使用
Promise介绍
Promise是一个类,可以翻译为承诺、期约
Promise 就像一个容器,里面存放着未来才会结束,返回结果的容器,返回的结果只需要在出口处接收就好了。从语法上讲,Promise
是一个对象,从它可以获取异步操作的消息。
当通过new创建Promise实例时,需要传入一个回调函数,我们称之为executor
- 这个回调函数会被立刻执行,并传入两个回调参数resolve、reject
- 当调用resolve回调函数时,会执行 Promise 对象的then()第一个方法传入的回调
- 当调用reject回调函数时,会执行 Promise 对象的then()第二个方法或catch方法传入的回调
Promise是一个状态机,分为 3 种状态:
- pending:待定状态,执行了 executor 后,处于该状态
- fulfilled:兑现(成功)状态,调用resolve()后,Promise 的状态更改为 fullfilled,且无法再次更改
- rejected:拒绝状态,调用reject()后,Promise 的状态更改为 rejected,且无法再次更改
Promise 的状态,只可能是其中一种状态,从进行中变为成功或失败状态之后,状态就固定了,不会再发生改变。
const p = new Promise((resolve,reject)=>{setTimeout(()=>{reject('error message')},1000)}).then(res=>{console.log(res)//不执行},err1=>{console.log('err1',err1)//1秒后打印 err1 error message 不会打印err2 error message 先被then的err1输出结果
}).catch(err2=>{console.log('err2',err2)//如果没有err1 1秒后打印err2 error message
})
手写Promise(resolve,reject)
Promise使用:
从Promise的使用上看,整理一下思路:Promise有三种状态。pending,fulfilled,rejected,且
- 执行了resolve,Promise状态会变成fulfilled;
- 执行了reject,Promise状态会变成rejected;
- Promise状态不可逆,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected;只会执行resolve或reject其中一个
- Promise中有throw的话,就相当于执行了reject;
先来实现1和2
- 构建 Promise 对象时,需要传入一个 executor 函数,Promise 的主要业务流程都在 executor 函数中执行。
- 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。
class MyPromise{constructor(executor){this.initValue() //执行传进来的执行函数// let executor =(resolve,reject)=>{// setTimeout(()=>{// reject('error message')// },1000)// }// executor()executor(this.resolve,this.reject)//resolve,reject}//先创建Promise初始化initValue(){this.PromiseState='pending'//Promise的状态this.PromiseResult=null//Promise的输出结果}resolve(value){console.log('resolve')this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,this.PromiseResult=value//Promise的输出结果}reject(value){console.log('reject')this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果}}
let p1 = new MyPromise((resolve, reject) => {// console.log('MyPromise',this)//此时的this是windowresolve('success')//如果没有改变resolve函数内部的this指向,内部的this是没有PromiseState和PromiseResult属性的// reject('fail')
})
console.log('p1', p1)
报错,resolve和reject内部的this是undefined,此时的resolve和reject在p1的原型对象上,如图
new的时候只会把构造函数里的类属性指向实例,类原型对象上方法不会指向实例
p1.resolve()可以调用,但是我们使用是resolve(),没有调用者。所以我们需要改变resolve函数的this指向,让resolve函数指向实例,才能访问实例上的属性(原型对象不能指回实例,原型对象和实例是1对多的关系,不清楚原型和实例关系的可以去本专栏的第一篇)
改变resolve函数的this指向,有三种方式call,apply,bind(详细可以看专栏第二篇),我们采用bind(只改变函数指向,先不执行)
class MyPromise{constructor(executor){this.initValue() this.initBind() //执行传进来的执行函数executor(this.resolve,this.reject)//resolve,reject// let executor =(resolve,reject)=>{// setTimeout(()=>{// reject('error message')// },1000)// }// executor()}// 先创建Promise初始化initValue(){this.PromiseState='pending'//Promise的状态this.PromiseResult=null//Promise的输出结果}//绑定this指向initBind(){//console.log('未绑定的this',this)//没有改变resolve和reject的this指向,此时resolve和reject函数是在class上,也就是实例的原型上this.resolve = this.resolve.bind(this)//对class上的resolve函数,改变this指向,指向实例。this是实例,resolve不在实例上,实例上找不到,顺着在实例的原型上找//等价于 this.resolve =this.__proto__.resolve.bind(this) ,在实例上创建一个函数,函数赋值原型上的函数resolve,此时函数内部的this指向是实例this.reject = this.reject.bind(this)console.log('绑定后的this',this)}resolve(value){console.log('resolve')this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,//所以需要将resolve的this指向指向PromiseState的同一个实例,构造函数里的属性会挂在new出来的实例,也就是,要指向p1this.PromiseResult=value//Promise的输出结果}reject(value){console.log('reject')this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果}}
let p1 = new MyPromise((resolve, reject) => {console.log('MyPromise',this)//此时的this是windowresolve('success')//resolve的调用者是window,如果没有改变resolve函数内部的this指向,内部的this是没有PromiseState和PromiseResult属性的reject('fail')
})
console.log('p1', p1)
绑定后,实例上不仅有俩个属性,还多了俩个方法
最后处理一下
3. Promise状态不可逆,第一次成功就永久为fulfilled,第一次失败就永远状态为rejected;只会执行resolve或reject其中一个
4. Promise中有throw的话,就相当于执行了reject;
在resolve、reject中添加判断,当状态不是pending时,说明被改变过,执行过resolve或reject
resolve(value){if(this.PromiseState!='pending'){return}this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,//所以需要将resolve的this指向指向PromiseState的同一个实例,构造函数里的属性会挂在new出来的实例,也就是,要指向p1this.PromiseResult=value//Promise的输出结果}reject(value){if(this.PromiseState!='pending'){return}this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果}
Promise中有throw的话,就相当于执行了reject。这就要使用try catch了
try {// 执行传进来的函数executor(this.resolve, this.reject)} catch (e) {// 捕捉到错误直接执行rejectthis.reject(e)}
手写Promise(then)
then的使用
// 马上输出 ”success“
const p1 = new Promise((resolve, reject) => {resolve('success')
}).then(res => console.log(res), err => console.log(err))// 1秒后输出 ”fail“
const p2 = new Promise((resolve, reject) => {setTimeout(() => {reject('fail')}, 1000)
}).then(res => console.log(res), err => console.log(err))// 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {resolve(100)
}).then(res => 2 * res, err => console.log(err)).then(res => console.log(res), err => console.log(err))
根据上述代码可以确定:
- then接收两个回调,一个是成功回调,一个是失败回调;
- 当Promise状态为fulfilled执行成功回调,为rejected执行失败回调;
- 如resolve或reject在定时器里,则定时器结束后再执行then;
- then支持链式调用,下一次then执行受上一次then返回值的影响;
实现1和2
then(onFulfilled, onRejected) {// 接收两个回调 onFulfilled, onRejected// 参数校验,确保一定是函数onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }if (this.PromiseState === 'fulfilled') {// 如果当前为成功状态,执行第一个回调onFulfilled(this.PromiseResult)} else if (this.PromiseState === 'rejected') {// 如果当前为失败状态,执行第二哥回调onRejected(this.PromiseResult)}}
完整代码为
class MyPromise {// 构造方法constructor(executor) {// 初始化值this.initValue()// 初始化this指向this.initBind()try {// 执行传进来的函数executor(this.resolve, this.reject)} catch (e) {// 捕捉到错误直接执行rejectthis.reject(e)}}initBind() {// 初始化thisthis.resolve = this.resolve.bind(this)this.reject = this.reject.bind(this)}initValue() {// 初始化值this.PromiseResult = null // 终值this.PromiseState = 'pending' // 状态}resolve(value) {// state是不可变的if (this.PromiseState !== 'pending') return// 如果执行resolve,状态变为fulfilledthis.PromiseState = 'fulfilled'// 终值为传进来的值this.PromiseResult = value}reject(reason) {// state是不可变的if (this.PromiseState !== 'pending') return// 如果执行reject,状态变为rejectedthis.PromiseState = 'rejected'// 终值为传进来的reasonthis.PromiseResult = reason}then(onFulfilled, onRejected) {// 接收两个回调 onFulfilled, onRejected// 参数校验,确保一定是函数onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }if (this.PromiseState === 'fulfilled') {// 如果当前为成功状态,执行第一个回调onFulfilled(this.PromiseResult)} else if (this.PromiseState === 'rejected') {// 如果当前为失败状态,执行第二哥回调onRejected(this.PromiseResult)}}
}
测试:
// 输出 ”success“
const test = new MyPromise((resolve, reject) => {resolve('success')
}).then(res => console.log(res), err => console.log(err))
实现第3点:如resolve或reject在定时器里,则定时器结束后再执行then;
这里先明白一个概念,
const test = new MyPromise((resolve, reject) => {resolve('success')
}).then(res => console.log(res), err => console.log(err))
resolve .then调用函数是同步的,即MyPromise创建,马上执行resolve改变状态,马上执行then里的判断状态,状态是成功,走成功分支,状态是失败走失败分支
当resolve外层套一个定时器(resolve还未执行),同步进入then的时候,状态还没改变,是pending。then函数就没有反馈。
为了解决这个问题,我们需要在then里对状态为pending的时候处理,存储onFulfilled和onRejected
Promise A规范里 存在多次then调用情况
- 当 Promise 成功执⾏时,所有 onFulfilled 需按照其注册顺序依次回调。
- 当 Promise 被拒绝执⾏时,所有的onRejected需按照其注册顺序依次回调。
具体Promise/A+规范,可以看http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/,或者我的3.2、理解Promise/A+规范意味着:我们可以多次调用,可以const p=promise,p.then(),p.then(),p.then()
因为同一个实例可能存在多次then调用,多个成功回调,多个失败回调,所以我们需要用队列存储。当resolve或者reject执行的时候,依次执行存储的成功队列或失败队列。一个实例只会promise只会执行resolve或reject里的一个,成功,则每个then都是执行成功,失败,则每个then都是执行失败。
另外:链式调用的时候promise.then().then().then(),每个then都返回的promise实例对象(下面详细讲)。对第三个then来说,他的调用者是p2=promise.then().then(),p2执行resolve或者reject之后,结果返回第三个then的onFulfilled或onRejected。也就是说单个链式调用里的then都是一个成功函数,一个失败函数。用回调队列来存储函数,不是因为链式调用的多个then。
实现:
class MyPromise{constructor(executor){this.initValue() this.initBind() try {//执行传进来的执行函数executor(this.resolve,this.reject)//resolve,reject// let executor =(resolve,reject)=>{// setTimeout(()=>{// reject('error message')// },1000)// }// executor()} catch (e) {// 捕捉到错误直接执行rejectthis.reject(e)}}// 先创建Promise初始化initValue(){this.PromiseState='pending'//Promise的状态this.PromiseResult=null//Promise的输出结果this.onFulfilledCallbacks=[]//成功回调this.onRejectedCallbacks=[]//失败回调}//绑定this指向initBind(){//console.log('未绑定的this',this)//没有改变resolve和reject的this指向,此时resolve和reject函数是在class上,也就是实例的原型上this.resolve = this.resolve.bind(this)//对class上的resolve函数,改变this指向,指向实例。this是实例,resolve不在实例上,实例上找不到,顺着在实例的原型上找//等价于 this.resolve =this.__proto__.resolve.bind(this) ,在实例上创建一个函数,函数赋值原型上的函数resolve,此时函数内部的this指向是实例this.reject = this.reject.bind(this)// console.log('绑定后的this',this)}resolve(value){if(this.PromiseState!='pending'){return}this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,//所以需要将resolve的this指向指向PromiseState的同一个实例,构造函数里的属性会挂在new出来的实例,也就是,要指向p1this.PromiseResult=value//Promise的输出结果//执行resolve改变状态后,当成功回调里有值时,执行,注意先进先出while(this.onFulfilledCallbacks.length){this.onFulfilledCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}reject(value){if(this.PromiseState!='pending'){return}this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果while(this.onRejectedCallbacks.length){this.onRejectedCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }if(this.PromiseState=='fulfilled'){onFulfilled(this.PromiseResult)}else if(this.PromiseState=='rejected'){onRejected(this.PromiseResult)}else{//暂存函数this.onFulfilledCallbacks.push(onFulfilled.bind(this))//onFulfilled是箭头函数的话,不bind也没影响。bind可以将onFulfilled挂在实例上。//onFulfilled一会儿会在resolve上执行,不改变this指向的话,onFulfilled也能执行,不过内部的this不是实例this.onRejectedCallbacks.push(onRejected.bind(this))}}}// 链式调用 输出 200
const p3 = new MyPromise((resolve, reject) => {setTimeout(()=>{resolve(100)},100)
})
p3.then(res => console.log('res',res), err => console.log('err',err))
p3.then(res => console.log('res2',res*2), err => console.log('err2',err))
p3.then(res => console.log('res3',res*3), err => console.log('err3',err))
//依次输出
// res 100
// res2 200
// res3 300
实现最后一点:then支持链式调用,下一次then执行受上一次then返回值的影响;
思路:链式调用,意味着调用then(),返回的结果也是个promise
1.如果回调函数返回的是Promise对象,根据返回的Promise的状态来决定then方法返回的Promise对象的状态,
const test1 = new Promise((resolve, reject) => {resolve(100) })//执行成功分支的Promise,该Promise返回reject,所以test2的返回结果与该Promise一致,走reject
const test2=test1.then(res => new Promise((resolve, reject) => reject(2 * res)), err => new Promise((resolve, reject) => resolve(3 * err)))//
const test3= test2.then(res => console.log('success', res), err => console.log('fail', err))// 输出 状态:fail 值:200
- 如果回调函数返回的是非Promise对象,then方法放回的Promise对象状态为fulfilled
const test4 = new Promise((resolve, reject) => {reject(100)
}).then(res => 2 * res, err => 3 * err)//执行err分支,3 * err,执行成功.then(res => console.log('success', res), err => console.log('fail', err))// 输出 success 300
- 如果回调的不是函数,需要进行包装,且值向下传递,即res=>res,err=>err(无视回调)
const promise1= new Promise((resolve, reject) => {resolve(100)
})const promise2 =promise1.then(200,'据因') promise2.then(res => console.log('res',res), err => console.log('err',err)) //'res',100(返回promise1的value值100,不是200)const promise3 = new Promise((resolve, reject) => {reject(100)
}).then(200,'据因') promise2.then(res => console.log('res',res), err => console.log('err',err)) //'err',据因
实现代码:
class MyPromise{constructor(executor){this.initValue() this.initBind() try {//执行传进来的执行函数executor(this.resolve,this.reject)//resolve,reject// let executor =(resolve,reject)=>{// setTimeout(()=>{// reject('error message')// },1000)// }// executor()} catch (e) {// 捕捉到错误直接执行rejectthis.reject(e)}}// 先创建Promise初始化initValue(){this.PromiseState='pending'//Promise的状态this.PromiseResult=null//Promise的输出结果this.onFulfilledCallbacks=[]//成功回调this.onRejectedCallbacks=[]//失败回调}//绑定this指向initBind(){//console.log('未绑定的this',this)//没有改变resolve和reject的this指向,此时resolve和reject函数是在class上,也就是实例的原型上this.resolve = this.resolve.bind(this)//对class上的resolve函数,改变this指向,指向实例。this是实例,resolve不在实例上,实例上找不到,顺着在实例的原型上找//等价于 this.resolve =this.__proto__.resolve.bind(this) ,在实例上创建一个函数,函数赋值原型上的函数resolve,此时函数内部的this指向是实例this.reject = this.reject.bind(this)// console.log('绑定后的this',this)}resolve(value){if(this.PromiseState!='pending'){return}this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,//所以需要将resolve的this指向指向PromiseState的同一个实例,构造函数里的属性会挂在new出来的实例,也就是,要指向p1this.PromiseResult=value//Promise的输出结果//执行resolve改变状态后,当成功回调里有值时,执行,注意先进先出while(this.onFulfilledCallbacks.length){this.onFulfilledCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}reject(value){if(this.PromiseState!='pending'){return}this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果while(this.onRejectedCallbacks.length){this.onRejectedCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }var thenPromise= new MyPromise((resolve, reject) => {if(this.PromiseState=='fulfilled'){// 接收一下回调函数的结果 PromiseResult this.PromiseResult=value就是onFulfilled的参数let result= onFulfilled(this.PromiseResult)//如果回调函数是一个Promise,那么then的结果跟该Promise一致。如果回调函数不是Promise,那么把该值或表达式作为resolve的参数传递出去if(result instanceof MyPromise){thenPromise.then(v => {//该promise走成功,则resolve,走失败,则reject// 如果返回的是resolve状态resolve(v)}, r => {reject(r);})}else{resolve(result)}}else if(this.PromiseState=='rejected'){//失败分支同理let result= onRejected(this.PromiseResult)//如果回调函数是一个Promise,那么then的结果跟该Promise一致。如果回调函数不是Promise,那么把该值或表达式作为resolve的参数传递出去if(result instanceof MyPromise){thenPromise.then(v => {//该promise走成功,则resolve,走失败,则reject// 如果返回的是resolve状态resolve(v)}, r => {reject(r);})}else{resolve(result)}}else if(this.PromiseState=='pending'){//暂存函数//要判断onFulfilled的执行结果。可以新建一个函数resultPromise,在函数内对函数结果判断。把新函数push进栈里,先不执行,等resolve上执行//因为成功和失败的执行逻辑一样,只是调用的函数名onFulfilled、onRejected不同,所以传参区分this.onFulfilledCallbacks.push(onFulfilled.bind(this))//onFulfilled是箭头函数的话,不bind也没影响。bind可以将onFulfilled挂在实例上。//onFulfilled一会儿会在resolve上执行,不改变this指向的话,onFulfilled也能执行,不过内部的this不是实例this.onRejectedCallbacks.push(onRejected.bind(this))}})return thenPromise}}
验证之后,发现成功和失败回调都是正常的。现在来处理状态pending分支,这里我们的onFulfilled或onRejected函数还没执行,需要等会儿执行。
- 我们可以在外面包一层函数,新建函数resultPromise,在栈中push 这个resultPromise函数
- onFulfilled和onRejected处理逻辑一致,只是函数名不同,可以进行传参封装
- 同时可以替换fulfilled和rejected分支里的代码,改为resultPromise(onFulfilled)
最后:then方法是微任务(后续事件循环章节总结会讲),所以可以套一个setTimeout代替,模拟实现异步(setTimeout为宏任务,此处主要跟在全局上的console对比)
最终实现代码:
class MyPromise{constructor(executor){this.initValue() this.initBind() try {//执行传进来的执行函数executor(this.resolve,this.reject)//resolve,reject} catch (e) {// 捕捉到错误直接执行rejectthis.reject(e)}}// 先创建Promise初始化initValue(){this.PromiseState='pending'//Promise的状态this.PromiseResult=null//Promise的输出结果this.onFulfilledCallbacks=[]//成功回调this.onRejectedCallbacks=[]//失败回调}//绑定this指向initBind(){//console.log('未绑定的this',this)//没有改变resolve和reject的this指向,此时resolve和reject函数是在class上,也就是实例的原型上this.resolve = this.resolve.bind(this)//对class上的resolve函数,改变this指向,指向实例。this是实例,resolve不在实例上,实例上找不到,顺着在实例的原型上找//等价于 this.resolve =this.__proto__.resolve.bind(this) ,在实例上创建一个函数,函数赋值原型上的函数resolve,此时函数内部的this指向是实例this.reject = this.reject.bind(this)// console.log('绑定后的this',this)}resolve(value){if(this.PromiseState!='pending'){return}this.PromiseState='fulfilled'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是window,//所以需要将resolve的this指向指向PromiseState的同一个实例,构造函数里的属性会挂在new出来的实例,也就是,要指向p1this.PromiseResult=value//Promise的输出结果//执行resolve改变状态后,当成功回调里有值时,执行,注意先进先出while(this.onFulfilledCallbacks.length){this.onFulfilledCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}reject(value){if(this.PromiseState!='pending'){return}this.PromiseState='rejected'//Promise的状态 //此时this是undefind,因为resolve的调用者指向的是windowthis.PromiseResult=value//Promise的输出结果while(this.onRejectedCallbacks.length){this.onRejectedCallbacks.shift()(this.PromiseResult)//shift()获取数组第一个元素,且改变原数组//获取的第一个元素是一个函数,执行函数}}then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val //包装函数,相当于res=>res,即不对结果进行处理,向下传递onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } //包装函数,相当于err=>err,即不对结果进行处理,向下传递,同时抛出异常var thenPromise= new MyPromise((resolve, reject) => {const resultPromise=(FnName)=>{setTimeout(() => { //模拟实现then异步任务try{// 接收一下回调函数的结果 PromiseResult this.PromiseResult=value就是onFulfilled的参数let result= FnName(this.PromiseResult)if (result === thenPromise) {// 不能返回自身哦throw new Error('不能返回自身。。。')}//如果回调函数是一个Promise,那么then的结果跟该Promise一致。如果回调函数不是Promise,那么把该值或表达式作为resolve的参数传递出去if(result instanceof MyPromise){thenPromise.then(v => {//该promise走成功,则resolve,走失败,则reject// 如果返回的是resolve状态resolve(v)}, r => {reject(r);})}else{resolve(result)}} catch(err){reject(err)throw new Error(err)}})}if(this.PromiseState=='fulfilled'){//调用函数resultPromise(onFulfilled)}else if(this.PromiseState=='rejected'){//失败分支同理resultPromise(onRejected)}else{//暂存函数//要判断onFulfilled的执行结果。可以新建一个函数resultPromise,在函数内对函数结果判断。把新函数push进栈里,先不执行,等resolve上执行//因为成功和失败的执行逻辑一样,只是调用的函数名onFulfilled、onRejected不同,所以传参区分this.onFulfilledCallbacks.push(resultPromise.bind(this,onFulfilled))//onFulfilled是箭头函数的话,不bind也没影响。bind可以将onFulfilled挂在实例上。//onFulfilled一会儿会在resolve上执行,不改变this指向的话,onFulfilled也能执行,不过内部的this不是实例this.onRejectedCallbacks.push(resultPromise.bind(this,onRejected))}})return thenPromise}}
Promise相关 API实现
all
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
- 如果所有Promise都成功,则返回成功结果数组;
- 如果有一个Promise失败,则返回这个失败结果;
实现思路:
定义一个数组和计数器,当Promise成功的时候数组添加成功回调值,计数器加1,同时判断此时的计数器与Promise数组长度是否相同,相同resolve(数组)
static all(promises) {const result = []let count = 0return new MyPromise((resolve, reject) => {const addData = (index, value) => {result[index] = valuecount++if (count === promises.length) resolve(result)}promises.forEach((promise, index) => {if (promise instanceof MyPromise) {promise.then(res => {addData(index, res)}, err => reject(err))} else {addData(index, promise)}})})
}
race
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
- 哪个Promise最快得到结果,就返回那个结果,无论成功失败;
实现思路:
遍历循环Promise数组,都进行输出,哪个结果最快,就会输出,返回那个值,利用Promise的状态一旦改变,就不会更改,只执行第一个resolve或reject
static race(promises) {return new MyPromise((resolve, reject) => {promises.forEach(promise => {if (promise instanceof MyPromise) {promise.then(res => {resolve(res)}, err => {reject(err)})} else {resolve(promise)}})})
}
allSettled
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
- 把每一个Promise的结果,集合成数组后返回;
实现思路:
定义一个数组和计数器,当Promise数组的每次Promise调用,数组添加回调值,计数器加1,当计数器与Promise数组长度相同时,返回esolve(数组)
static allSettled(promises) {return new Promise((resolve, reject) => {const res = []let count = 0const addData = (status, value, i) => {res[i] = {status,value}count++if (count === promises.length) {resolve(res)}}promises.forEach((promise, i) => {if (promise instanceof MyPromise) {promise.then(res => {addData('fulfilled', res, i)}, err => {addData('rejected', err, i)})} else {addData('fulfilled', promise, i)}})})
}
any
与all相反
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功;
- 如果有一个Promise成功,则返回这个成功结果;返回第一个兑现的值
- 如果所有Promise都失败,则报错;
实现思路:
定义计数器,当Promise数组的每次Promise调用,当成功的时候,直接返回成功(第一个成功的返回,和race一样);失败的时候,计数器加1,当计数器与Promise数组长度相同时,返回报错信息
static any(promises) {return new Promise((resolve, reject) => {let count = 0promises.forEach((promise) => {promise.then(val => {resolve(val)}, err => {count++if (count === promises.length) {reject(new AggregateError('All promises were rejected'))}})})})
}
}
思考题(下一章讲)
实现一个带并发限制的异步调度器 Scheduler,保证同时运行的任务最多有N个。完善下面代码中的 Scheduler 类,使得以下程序能正确输出:
class Scheduler {add(promiseCreator) { ... }// ...
}const timeout = (time) => new Promise(resolve => {setTimeout(resolve, time)
})const scheduler = new Scheduler(n)
const addTask = (time, order) => {scheduler.add(() => timeout(time)).then(() => console.log(order))
}addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')// 打印顺序是:2 3 1 4
async/await和Promise的关系
async/await和Promise的对应关系:async封装Promise,await相当then,try…catch相当于catch
1. 执行async函数,返回的是Promise对象
async function fn1(){return 100 //相当于return Promise.resolve(100)
}
const res1=fn1()//执行async函数,返回的是一个Promise对象,比如上面的return 100封装成了一个Promise对象进行返回
console.log('res1',res1)//Promise对象
res1.then(data=>{console.log('data',data)//100
})
//可以用const data= await fn1()接收data值 使用await,要和async配套
2. await相当于Promise的then
!( async function(){const res2=Promise.resolve(100)//相当于上面例子的res1 也就是fn1()const data= await res2 //await相当于Promise的then res1.then(data=>{})console.log('data',data)//100
})()!( async function(){const res2=await 400 //await Promise.resolve(400) await后面不跟Promise,也会被封装成Promiseconsole.log('res2',res2)//400
})()
3. try…catch 可捕获异常,代替了Promise的catch
!( async function(){const p4=Promise.reject('err')//rejected状态try{const res=await p4 //await相当于then,但是reject不会触发thenconsole.log(res) //不会输出,因为const res=await p4被报错,被catch捕获} catch(ex){console.error(ex)//try...catch 相当于Promise的catch}})()
async/await的使用
async/await的用处:用同步方式,执行异步操作
现在有一个新的要求:先请求完接口1,再拿接口1返回的数据,去当做接口2的请求参数,那我们可以这么做:
Promise做法
function request(num) { // 模拟接口请求return new Promise(resolve => {setTimeout(() => {resolve(num * 2)}, 1000)})
}request(5).then(res1 => {console.log(res1) // 1秒后 输出 10request(res1).then(res2 => {console.log(res2) // 2秒后 输出 20})
})
async/await做法
function request(num) { // 模拟接口请求return new Promise(resolve => {setTimeout(() => {resolve(num * 2)}, 1000)})
}
async function fn () {const res1 = await request(5)const res2 = await request(res1)console.log(res2) // 2秒后输出 20
}
fn()
在async函数中,await规定了异步操作只能一个一个排队执行,从而达到用同步方式,执行异步操作的效果。
注意:await只能在async函数中使用
刚刚上面的例子await后面都是跟着异步操作Promise,那如果不接Promise?
function request(num) { // 去掉PromisesetTimeout(() => {console.log(num * 2)}, 1000)
}async function fn() {await request(1) // 2await request(2) // 4// 1秒后执行完 同时输出
}
fn()
可以看出,如果await后面接的不是Promise的话,等同于async/await不生效,等价于
function request(num) { // 去掉PromisesetTimeout(() => {console.log(num * 2)}, 1000)
}function fn() {request(1) // 2request(2) // 4// 1秒后执行完 同时输出
}
fn()
Q:什么是async?
async是一个位于function之前的前缀,只有async函数中,才能使用await。
async执行完是返回一个 Promise ,状态为fulfilled,值是function的返回值
async function fn (num) {return num //相当于return Promise.resolve(num)
}
console.log(fn) // [AsyncFunction: fn]
console.log(fn(10)) // Promise {<fulfilled>: 10}
fn(10).then(res => console.log(res)) // 10
总结
- await只能在async函数中使用,不然会报错;
- async函数返回的是一个Promise对象,有无值看有无return值;
- await后面最好是接Promise,虽然接其他值也能达到排队效(但是作用等于原来不使用await的时候);
- async/await作用是用同步方式,执行异步操作
- async/await是一种语法糖,用到的是ES6里的迭代函数——generator函数(可以自行了解)(ES6的class也是语法糖,用普通function也能实现同样效果)
相关文章:
3.1、前端异步编程(超详细手写实现Promise;实现all、race、allSettled、any;async/await的使用)
前端异步编程规范 Promise介绍手写Promise(resolve,reject)手写Promise(then)Promise相关 API实现allraceallSettledany async/await和Promise的关系async/await的使用 Promise介绍 Promise是一个类,可以翻…...
3.1. 马氏链-马氏链的定义和示例
马氏链的定义和示例 马氏链的定义和示例1. 马氏链的定义2. 马氏链的示例2.1. 随机游走2.2. 分支过程2.3. Ehrenfest chain2.4. 遗传模型2.5. M/G/1 队列 马氏链的定义和示例 1. 马氏链的定义 对于可数状态空间的马氏链, 马氏性指的是给定当前状态, 其他过去的状态与未来的预测…...
红利之外的A股底仓选择:A50
内容提要 华泰证券指出,当前指数层面下行风险不大,市场再入震荡期下,可关注三条配置线索:1)A50为代表的产业巨头;2)以家电/食饮/物流/出版为代表的稳健消费龙头,3)消费电…...
wondershaper 一款限制 linux 服务器网卡级别的带宽工具
文章目录 一、关于wondershaper二、文档链接三、源码下载四、限流测试五、常见报错1. /usr/local/sbin/wondershaper: line 145: tc: command not found2. Failed to download metadata for repo ‘appstream‘: Cannot prepare internal mirrorlist: No URLs.. 一、关于wonder…...
独孤思维:盲目进群,根本赚不到钱
01 我看有些伙伴,对标同行找写作素材和灵感的时候。 喜欢把对标文章发给ai提炼总结。 这个方法好是好,但是,有一个问题。 即,无法感受全文的细节。 更无法感受作者的情感和温度。 就好像电影《记忆大师》一样。 我提取了记…...
针对indexedDB的简易封装
连接数据库 我们首先创建一个DBManager类,通过这个类new出来的对象管理一个数据库 具体关于indexedDB的相关内容可以看我的这篇博客 indexedDB class DBManager{}我们首先需要打开数据库,打开数据库需要数据库名和该数据库的版本 constructor(dbName,…...
网络编程--网络理论基础(二)
这里写目录标题 网络通信流程mac地址、ip地址arp协议交换机路由器简介子网划分网关 路由总结 为什么ip相同的主机在与同一个互联网服务通信时不冲突公网ip对于同一个路由器下的不同设备,虽然ip不冲突,但是因为都是由路由器的公网ip转发通信,接…...
Python MongoDB 基本操作
本文内容主要为使用Python 对Mongodb数据库的一些基本操作整理。 目录 安装类库 操作实例 引用类库 连接服务器 连接数据库 添加文档 添加单条 批量添加 查询文档 查询所有文档 查询部分文档 使用id查询 统计查询 排序 分页查询 更新文档 update_one方法 upd…...
Node.js 入门:
Node.js 是一个开源、跨平台的 JavaScript 运行时环境,它允许开发者在浏览器之外编写命令行工具和服务器端脚本。以下是一些关于 Node.js 的基础教程: 1. **Node.js 入门**: - 了解 Node.js 的基本概念,包括它是一个基于 Chro…...
java8 List的Stream流操作 (实用篇 三)
目录 java8 List的Stream流操作 (实用篇 三) 初始数据 1、Stream过滤: 过滤-常用方法 1.1 筛选单元素--年龄等于18 1.2 筛选单元素--年龄大于18 1.3 筛选范围--年龄大于18 and 年龄小于40 1.4 多条件筛选--年龄大于18 or 年龄小于40 and sex男 1.5 多条件筛…...
机器学习python实践——数据“相关性“的一些补充性个人思考
在上一篇“数据白化”的文章中,说到了数据“相关性”的概念,但是在统计学中,不仅存在“相关性”还存在“独立性”等等,所以,本文主要对数据“相关性”进行一些补充。当然,如果这篇文章还能入得了各位“看官…...
MySQL——触发器(trigger)基本结构
1、修改分隔符符号 delimiter $$ $$可以修改 2、创建触发器函数名称 create trigger 函数名 3、什么样在操作触发,操作哪个表 after :……之后触发 before :……之后触发 insert :……之后触发 update :……之后触…...
数字孪生定义及应用介绍
数字孪生定义及应用介绍 1 数字孪生(Digital Twin, DT)概述1.1 定义1.2 功能1.3 使用场景1.4 数字孪生三步走1.4.1 数字模型1.4.2 数字影子1.4.3 数字孪生 数字孪生地球平台Earth-2 参考 1 数字孪生(Digital Twin, DT)概述 数字孪…...
数据赋能(122)——体系:数据清洗——技术方法、主要工具
技术方法 数据清洗标准模型是将数据输入到数据清洗处理器,通过一系列步骤“清理”数据,然后以期望的格式输出清理过的数据。数据清洗从数据的准确性、完整性、一致性、惟一性、适时性、有效性几个方面来处理数据的丢失值、越界值、不一致代码、重复数据…...
【SCAU数据挖掘】数据挖掘期末总复习题库简答题及解析——中
1. 某学校对入学的新生进行性格问卷调查(没有心理学家的参与),根据学生对问题的回答,把学生的性格分成了8个类别。请说明该数据挖掘任务是属于分类任务还是聚类任务?为什么?并利用该例说明聚类分析和分类分析的异同点。 解答: (a)该数据…...
2024年注册安全工程师报名常见问题汇总!
注册安全工程师报名 24年注册安全工程师报名已正式拉开序幕,报名时间为6月18日—7月10日,考试时间为10月26日—10月27日。 目前经有12个地区公布了2024年注册安全工程师报名时间: 注册安全工程师报名信息完善 根据注安报名系统提示&am…...
JRebel-JVMTI [FATAL] Couldn‘t write to C:\Users\中文用户名-完美解决
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 热部署下载参考博客解决第一步第二步第三步:第四步: 热部署下载 下载后启动报错:JRebel-JVMTI [FATAL] Couldn’t write to C:\…...
STM32基于DMA数据转运和AD多通道
文章目录 1. DMA数据转运 1.1 初始化DMA步骤 1.2 DMA的库函数 1.3 设置当前数据寄存器 1.4 DMA获取当前数据寄存器 2. DMA数据转运 2.1 DMA.C 2.2 DMA.H 2.3 MAIN.C 3. DMAAD多通道 3.1 AD.C 3.2 AD.H 3.3 MAIN.C 1. DMA数据转运 对于DMA的详细解析可以看下面这篇…...
安卓应用开发——Android Studio中通过id进行约束布局
在Android开发中,布局通常使用XML文件来描述,而约束(如相对位置、大小等)可以通过多种方式实现,但直接使用ID进行约束并不直接对应于Android的传统布局系统(如LinearLayout、RelativeLayout等)。…...
Elasticsearch过滤器(filter):原理及使用
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 💥💥个人主页:奋斗的小羊 💥💥所属专栏:C语言 🚀本系列文章为个人学习…...
Docker配置与使用详解
一、引言 随着云计算和微服务的兴起,Docker作为一种轻量级的容器化技术,越来越受到开发者和运维人员的青睐。Docker通过容器化的方式,将应用程序及其依赖项打包成一个可移植的镜像,从而实现了应用程序的快速部署和扩展。本文将详…...
触控MCU芯片(1):英飞凌PSoC第6代第7代
前言: 说到触摸MCU芯片,这个历史也是很久了,比如日常经常接触到的洗衣机、电冰箱、小家电,隔着一层玻璃,轻轻一按就能识别按键,感觉比过去纯机械式的按键更高级更美观,不仅白电,现在很多汽车也都在进行触摸按键的改版,不再使用笨重的机械按键,比如空调调温按键、档位…...
git pull报错:unable to pull from remote repository due to conflicting tag(s)
背景 我在vscode里正常拉取代码,突然就报了如题所示的错误。 原因 因为vscode的拉取按钮执行的实际命令是:git pull --tags origin branch-name,该命令的实际含义是从远程仓库拉取指定的分支和该远程仓库上的所有标签。 在拉取标签时本地的…...
Python将字符串用特定字符分割并前面加序号
Python将字符串用特定字符分割并前面加序号 Python将字符串用特定字符分割并前面加序号,今天项目中就遇到,看着不难,得花点时间搞出来急用啊,在网上找了一圈,没发现有完整流程的文章。所以就搞出来并写了这个文章。仅…...
【第16章】Vue实战篇之跨域解决
文章目录 前言一、浏览器跨域二、配置代理1.公共请求2.代理配置 总结 前言 前后端项目分离衍生出浏览器跨域问题,开发之前我们通过配置代理解决这个问题。 一、浏览器跨域 浏览器的跨域问题主要是由于浏览器的同源策略导致的。同源策略是浏览器的一个安全功能&…...
【PB案例学习笔记】-22制作一个语音朗读金额小应用
写在前面 这是PB案例学习笔记系列文章的第22篇,该系列文章适合具有一定PB基础的读者。 通过一个个由浅入深的编程实战案例学习,提高编程技巧,以保证小伙伴们能应付公司的各种开发需求。 文章中设计到的源码,小凡都上传到了gite…...
glmark2代码阅读总结
glmark2代码阅读总结 一、总体 用输入参数生成testbench项用scene和benchmark管理进行复用通过类的重载,创建出不同的分支和具体的实现点,如scene和mainloop类用例执行又规划,每个scene都统一有setup,等使用scene的继承关系&…...
第 6 章 监控系统 | 监控套路 - 总结
前面,我们使用 Prometheus + Grafana + Node Exporter 实现虚拟机监控及告警。 那么,😇 监控的套路究竟是什么呢? 第 1 步:暴露 metrics,通过某个 exporter 将 metrics 暴露出来第 2 步:配置 Prometheus 抓取上面暴露的 metrics 数据第 3 步:加速 metrics 显示,配置…...
VsCode中C文件调用其他C文件函数失败
之前一直使用CodeBlocks,最近使用vscode多,感觉它比较方便,但在调用其他C文件的时候发现报错以下内容基于单C文件运行成功,否则请移步 博文:VSCode上搭建C/C开发环境 报错信息 没有使用CodeRunner插件,弹…...
css中content属性你了解多少?
在CSS中,content属性通常与伪元素(如 ::before 和 ::after)一起使用,用于在元素的内容之前或之后插入生成的内容。这个属性不接受常规的HTML内容,而是接受一些特定的值,如字符串、属性值、计数器值等。 以…...
网站设计模板/兰州seo公司
有什么用? 2017年初,整个全景相机产业经历了一轮小爆发。除了像Insta 360、德图等几家创业公司外,小米、努比亚等几家传统手机厂商在2017年4月份也相继发布了自己的消费级全景相机,紧接着,Facebook、Google互联网巨头…...
网页制作网站创建/浙江网站推广运营
Bessel函数介绍贝塞尔函数(Bessel functions)是数学上的一类特殊函数的总称。一般贝塞尔函数是下列常微分方程(一般称为贝塞尔方程)的标准解函数y(x):这类方程的解是无法用初等函数系统地表示的。贝塞尔函数的具体形式随上述方程中任意实数α变化而变化(相应地&…...
视频 播放网站怎么做的/潍坊网站建设公司
1 . 使用方式 :ln [option] source_file dist_file-f 建立时,将同档案名删除.-i 删除前进行询问.ln -s abc cde 建立abc 的软连接ln abc cde 建立abc的硬连接,2. 软链接与硬链接的区别(通俗):硬链接可认为是…...
永久免费网站建立/网络营销专业介绍
看门狗是当CPU进入错误状态后,无法恢复的情况下,使计算机重新启动 由于计算机在工作时不可避免的受到各种各样的因素干扰,即使再优秀的计算机程序也可能因为这种干扰使计算机进入一个死循环,更严重的就是导致死机。 有两种办法来处…...
免费网站赚钱/百度指数是免费的吗
点击上方“ 技成培训 ”,选择“置顶公众号”17万工控人关注的微信平台:技术分享、学习交流、工控视频将三菱可编程控制器中使用的内置的输入输出继电器、辅助继电器、状态、计数器、数据寄存器等各种软元件的作用和功能进行了说明。这些内容是使用可编程…...
如何在记事本中做网站链接/短视频代运营费用明细
集群的作用: 集群的方式有二种: 第二种的好处是:master宕机以后可以直接切换到slave1 主从通信的过程 先通过dump导出大块的rdb 然后把aof缓冲过来,然后通过replicationfeedslave保持联系 redis集群的配置 第一步: 复制…...