一步步实现aysnc await
一步步实现aysnc await

一步步实现aysnc await

async/await解决什么问题:

async/await可以解决promise.then语法链式调用的繁琐问题,通过.next()传参形式,把表达式的值重新注入到函数里面,可以像同步调用那样拿到异步函数的结果值,在调用generator函数时,里面的函数也不会全部执行,而是需要.next()一步一步运行到yield的节点,这样在yield表达式下的函数,就需要等到外部函数调用.next()之后调用,这样申明和调用函数的逻辑就可以写在一起。这个是通过Iterator无法实现的(下文会详细解释)

P.S.: 任何有疑问的地方都可以通过debugger来打断点去理解。通过npm run debug

前置知识:

async/await是generator/yield的语法糖

generator/yield[扩展阅读:generator/yield是什么?]

  • 语法:
// 声明一个Generator函数
function* gen () {  
  const result = yield 表达式
}
//  调用
const g = gen()
g.next(param)
g.throw(param)
g.return(param)

next/throw/return能让我们从外面向generator传值

next 执行yield的表达式,并得到{done ,value}对象,使外层函数知道generator是否已经执行完,yield的表达式求值后的结果是什么,next传递的参数,赋值给result

throw可以让generator抛出异常,终止generator,传递错误参数,会被内部的catch捕获使用

return可以让generator终止,value值为return传的参数

generator/yield提供了一种外部函数和内部函数传递数据的方式,实现逻辑分离,那么一步步调用next函数比较麻烦的,有没有一种自动执行的方法,自动执行就是通过递归并传递参数的形式实现的

那么我们先实现一个同步版本的自动执行器

代码地址

//  实现async,await
//  先考虑generation yield的同步版本
function* g() {
    const a = yield syncFn('hi')
    console.log('a', a)
    const b = yield syncErrorFn(a + 'sync')
    console.log('a', b)
    return b
}

function syncFn(a) {
    return a 
}

function syncErrorFn(a) {
    throw(new Error(a))
}

function syncStep(gen) {
    const g = gen()
    function autoRun(property, preVal) {
        let next;
        next = g[property](preVal)
        
        if(next.done) {
            return next.value
        }else{
            return autoRun('next', next.value)
        }
    }
    try {
        return autoRun('next', undefined)
    } catch (error) {
        console.log('outer', error.message)
    }
    
}

syncStep(g)

这里是同步调用,我们不需要担心抛错能不能被冒泡捕获,yield表达式如果抛错,generator函数就会终止迭代,理解了同步调用,那么异步调用只需要解决好promise.then的问题,把resolve的value返回到generator函数,或者reject的error带到函数终止迭代就可以了。

代码地址

//  实现异步版本
function* asyncG() {
    const a = yield asyncFn('hi')
    console.log('a', a)
    const b = yield asyncErrorFn(a + 'async')
    debugger
    console.log('b', b)
    return b    
}

function asyncFn(a) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(a)
        },1000)
    })
}

function asyncErrorFn(a) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(a)
        },500)
    })
}

function step(gen) {
    const g = gen()
    return new Promise((resolve, reject) => {
        function autoRun(property, preVal) {
            let next;
            try {
                next = g[property](preVal)
                debugger
            } catch (error) {
                return reject(error)    //  这里reject的同时需要终止递归
            }
            if(next.done) {
                return resolve(next.value)
            }
            next.value.then((resolve) => {
                autoRun("next", resolve)
            }).catch((error) => {
                autoRun("throw", error)
            })
        }
        autoRun('next', undefined)
    })
}

step(asyncG).then((resolve) => {
    console.log('step', resolve)
}).catch((error) => {
    console.log('stepError', error)
})

那我们再看看细看处理错误的问题,我们有时会遇到UnhandledPromiseRejectionWarning,这是哪里出问题了呢?

我们不再.catch捕获async返回的promise异常改成try/catch形式

代码地址

async function canNotCatchFn() {
  try {
    //  UnhandledPromiseRejectionWarning
    step(asyncG) //  这里会捕获不了错误,要不就是再包async,await,要不就.catch去捕获
    //  await  step(asyncG)
  } catch (error) {
    console.log(error)
  }
}

canNotCatchFn()

这里由于async内部有错误,而内部也没有try/catch去捕获,那么这个错误就会被autoRun执行的时候捕获到,然后将返回的promise的状态设为reject,而外部调用的时候没有处理这个reject就会导致UnhandledPromiseRejectionWarning,知道什么原因之后,要解决就很简单要不再包async,await,要不就.catch去捕获。如果在内部有try/catch去捕获,就不会走到autoRun的catch,而是继续执行到下一个yield。

那么这里总结一下错误处理:

  • 写在async/await内部,try/catch时需要注意,返回的值可能是undefined,因为reject的时候是拿不到值的,而try/catch之后的yield能正常调用【适用如果其中有一步异步出错,但下一步异步操作仍正常执行】
  • 写在调用async的时候,这时候函数只会走到抛出异常以前的await,得到的catch也是出异常的异步函数时抛出的reject【适用如果其中有一个异步出错了,就不行继续其他异步】
  • 可以不过早的捕获错误,写service层调用服务的时候,可以将捕获错误留到调用这个服务才写
  • 如果要用async/await内部捕获就要try/catch,直接调用async就要用.catch捕获,async函数如果没写try/catch想捕获同步错误,需要在外部调用的时候用.catch

代码地址

//  模拟service情况
const getEntityCatch = async() => {
  try {
    const data = await getFailEntity()
    return data
  } catch (error) {
    console.log('getEntityError', error)
  }
}
const getFailEntity = async() => {
  const data = await mockFetchData(false)
  return data
}
const getSuccessEntity = async() => {
  const data = await mockFetchData(true)
  return data
}

const mockFetchData = (success) => {
  return new Promise((resolve, reject) => {
    if(success) {
      resolve([1,2,3])
    }else{
      reject(new Error('fail'))
    }
  })
}

//  不相关的步骤想顺序执行
const uiNotRelativeFn = async() => {
  const data1 = await getEntityCatch()
  console.log('uiNotRelativeFn', data1)
  const data2 = await getSuccessEntity()  //  注意这里抛出的错误没有被捕获
  console.log('uiNotRelativeFn', data2)
}

uiNotRelativeFn()  // 如果data2抛出错误,则会捕获不到data2的错误

//  相关的步骤想顺序执行
const uiRelativeFn = async() => {
  try {
    const data1 = await getFailEntity()
    console.log('uiRelativeFn', data1)
    const data2 = await getSuccessEntity()
    console.log('uiRelativeFn', data2)
  } catch (error) {
    console.log('uiRelativeFnError', error)
  }
}

uiRelativeFn()

//  相关的步骤想顺序执行
const uiRelativeNotCatchFn = async() => {
    const data1 = await getFailEntity()
    console.log('uiRelativeNotCatchFn', data1)
    const data2 = await getSuccessEntity()
    console.log('uiRelativeNotCatchFn', data2)
}

const uiRelativeNotCatchNormalErrorFn = async() => {
    const data2 = await getSuccessEntity()
    console.log('uiRelativeNotCatchFn', data2)
    throw(new Error("sync Error"))
    const data1 = await getFailEntity()
    console.log('uiRelativeNotCatchFn', data1)
}

uiRelativeNotCatchNormalErrorFn().catch(error => {
  console.log('uiRelativeNotCatchNormalErrorFn', error)
})

下面来探讨一下如果不用generator,单纯通过Iterator方式要怎么实现

我们知道generator返回的就是一个迭代对象,那我们如果不用generator,能不能也实现同样的效果

我们来先看一般我们是怎么实现promise的顺序执行的

代码地址

// promise版本
function promiseFn() {
    return asyncFn("hi")
    .then((resolve) => asyncFn(resolve + "async"))
    .then((resolve) => asyncErrorFn(resolve))
}

promiseFn().then((resolve) => {
    console.log('promiseFn', resolve)
}).catch((error) => {
    console.log('promiseFnError', error)
})

如果我们想免去promise.then语法让他顺序执行,那么这些备调用的函数需要存在generator函数里面。我们用数组来存储他们

代码地址

// 自动执行promise版本
function autoRunPromiseFn(arr) {
    return new Promise((resolve, reject) => {
        function autoRun(value) {
            const promiseFn = arr.shift()
            debugger
            if(!promiseFn)return resolve(value)
            promiseFn(value).then((resolve) => {
                autoRun(resolve)
            }).catch((error) => {
                reject(error)
            })
        }
        autoRun(undefined)
    })
}

autoRunPromiseFn([() => asyncFn("hi"), (resolve) => asyncFn(resolve + "async"), (resolve) => asyncErrorFn(resolve)]).then((resolve) => {
    console.log('autoRunPromiseFn', resolve)
}).catch((error) => {
    console.log('autoRunPromiseError', error)
})

显然这样用数组,并没有比一般promise好多少,只是把promise.then这个去掉了,看起来还是比较复杂,那么我们用一个generator函数包含他们

代码地址

//  Iterator promise版本
const promiseNextFn = () => {
    let promiseFnArr = []
    let error = false
    return {
        next(...args){
            const promiseFn = promiseFnArr.shift()
            if(!promiseFn || error){
                return {
                    value: args,
                    done: true
                }
            }
            return {
                done: false,
                value: promiseFn(...args)
            }
        },
        setCurrentFn(fn){
            promiseFnArr.push(fn)
        },
        throw(value){
            error = true
            throw(new Error(value))
         }
    }
}
function promiseRunFn() {
    const g = promiseNextFn()
    g.setCurrentFn(() => {
        return asyncFn("hi")
    })
    g.setCurrentFn((resolve) => {
        return asyncFn(resolve + "async")
    })
    g.setCurrentFn((resolve) => {
        return asyncErrorFn(resolve)
    })
    return g
}

step(promiseRunFn).then((resolve) => {
    console.log('promiseFn', resolve)
}).catch((error) => {
    console.log('promiseFnError', error.message)
})

这里我们直接复用之前实现的async/await的step,但是没有了generator/yield,我们就要把声明异步函数和调用异步函数的逻辑分开,最后只能跟promise.then一样,但通过这个Iterator版本你会对generator有更深的了解,比如为什么要先执行一次generator函数,为什么会返回一个迭代对象,为什么抛错可以在generator内部和外部都能获取到

reference:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注