浅谈异步编程中错误的捕获

这篇具有很好参考价值的文章主要介绍了浅谈异步编程中错误的捕获。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

详解awai的异步编程场景

之前的文章说到,async和await可以取代生成器函数和yield的组合,实现优雅的异步操作写成同步写法。​那异步错误的捕获又该如何处理​?这篇文章我​将先讲async和await的特点,然后讲解​异步编程中错误的捕获。

一,async关键字

async关键字标记的函数,会变成异步函数,它的返回值和一般函数不同。

1,返回一个promise
2,返回一个thenable对象则和then方法中的resolve,或者reject有关
1.1,async的返回必然是一个promise

如果返回的是普通值,它会用promise.resolve()把它转化为promise的。

async function test(){
  return 'test'
}
const a=test()
console.log(a)//Promise { 'test' }

如果返回值是promise,那就走正常的promise逻辑,看它异步操作后的结果是成功还是失败。

async function test(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      reject("失败了")
    },1000)
  })
}
const a=test()
a.then((res)=>{
  console.log("成功了执行",res)
},(err)=>{
  console.log("失败了执行",err)//失败了执行 失败了
})
1.2,返回一个thenable对象

如果返回值是一个thenable对象,因为会递归执行其中的then方法,最后返回的promise就是这个thenable对象then方法中处理后的promise。

async function test1(){
  const thenableA = {
       name: '名字哦',
       then: function (resolve, reject) {
           console.log(`I'am ${this.name}`);
           resolve(this.name)
       }
   }
  return thenableA
}
const a=test1()//I'am 名字哦
setTimeout(()=>{
  console.log(a)//Promise { '名字哦' },注意到这里的promise取到了最终的值,而不是这个thenableA对象
})

二,await关键字

1,异步函数中可以使用await关键字,普通函数不行
2,通常await关键字后面都是跟一个Promise,这点和async的返回类似。并且await执行后返回的是该promise处理完成的value值.

await只能在async异步函数中使用,并且它返回的是promsie的结果值。这里可以看之前读取文件内容的代码:

const fs = require('fs')
function readFile(fileName){
    return new Promise((resolve,reject)=>{
        fs.readFile(fileName, (err,data) => {
            resolve(data.toString())
        })
    })
}
async function test() {
    //先按照顺序读取三个文件
   const txt1= await readFile('./text1.txt');
   const txt2= await readFile('./text2.txt');
   const txt3= await readFile('./text3.txt');
   //这里再用结果处理其他代码
   console.log(txt1,txt2,txt3)//第一个文件 第二个文件 第三个文件
}
test()

三,async/await的同步写法只是在同个async调用栈内生效

如下代码,成功了22222之后打印。是因为await把代码暂停。只会在async这个test的调用栈中生效。

function test1(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      resolve("成功了")
    },1000)
  })
}
async function test(){
    console.log("3333")
    const a =await test1()
    console.log(a)
}
console.log("11111")
test()
console.log("22222")
//打印结果
//11111
//3333
//22222
//成功了

四,asycn和await让异步操作排队完成与并行完成

4.1,串行完成异步操作

我们常规使用async/await的时候,如下代码,异步任务会按照顺序执行,一个结束之后才会执行下一个,这样会造成该调用栈内的代码非常耗时。

如下代码:

function test1(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(1)
      resolve("成功了第一个")
    },1000)
  })
}
function test2(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(2)
      resolve("成功了第二个")
    },2000)
  })
}
function test3(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(3)
      resolve("成功了")
    },3000)
  })
}
async function test(){
    await test1()
    await test2()
    await test3()
}
test()

这里的test函数中,三个异步操作会按照顺序完成,一个完成后才进行下一个,这就非常耗时,这里将花费6s的时间。在有些时候,我们更希望并行完成异步请求,把这个时间节省下来。

4.2,让异步请求并行完成
4.2.1,利用js的事件循环机制

当几个任务相互独立,没有啥依赖关系时:

function test1(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(1)
      resolve("成功了第一个")
    },1000)
  })
}
function test2(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(2)
      resolve("成功了第二个")
    },2000)
  })
}
function test3(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(3)
      resolve("成功了第三个")
    },3000)
  })
}
async function test(){
  const a=test1()
  const b=test2()
  const c= test3()
  const a1= await a
  const b1= await b
  const c1= await c
}
test()

当我们执行const a=test1();;const b=test2();const c= test3()的时候,因为promise执行构造函数的时候,是同步的,所以这三个异步操作已经在event Table中注册执行,可以理解为三个水壶同时插上电开始烧水了。(这个比喻可以看我这篇文章理解:js事件循环机制-宏任务微任务_笑道三千的博客-CSDN博客)

这时候,abc都是状态为pedding的promise。代码继续执行到await,因为await其实效果就是yield一样,会暂停代码的执行,内部使用的是如下:

Promise.resolve(p.value).then(res=>{
   _next(res);
})

这种方式处理,也就是await是调用这个then方法,返回这个promise的结果值(等状态变成fulfilled)。

那么说,三壶水都烧完是只过了3s的时间,第一个await在第一秒后有返回,第二个是接着执行代码,然后到第二个await,接着是第三个。由此实现并行执行异步操作。

4.2.2,利用promise.all方法

另外可以使用promise.all方法来实现,其实原理是一样的。但是它能等这几个并行的异步都完成后再拿结果来统一处理。

function test1(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(1)
      resolve("成功了第一个")
    },1000)
  })
}
function test2(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(2)
      resolve("成功了第二个")
    },2000)
  })
}
function test3(){
  return new Promise((resolve,reject)=>{
    setTimeout(()=>{
      console.log(3)
      resolve("成功了第三个")
    },3000)
  })
}
async function test(){
  const a=await Promise.all([test1(),test2(),test3()])
  console.log(a)
}
test()

其实这也是利用的js的事件循环机制,我们来看下promise.all的实现原理:

MyPromise.all = function(promiseList) {
  var resPromise = new MyPromise(function(resolve, reject) {
    var count = 0;
    var result = [];
    var length = promiseList.length;
    if(length === 0) {
      return resolve(result);
    }
    promiseList.forEach(function(promise, index) {
      MyPromise.resolve(promise).then(function(value){
        count++;
        result[index] = value;
        if(count === length) {
          //全部执行完毕后才resolve结果数组出去
          resolve(result);
        }
      }, function(reason){
        reject(reason);
      });
    });
  });
  return resPromise;
}

可以看到其内部也是使用的:

MyPromise.resolve(promise).then(function(value){
    count++;
    result[index] = value;
    if(count === length) {
        //全部执行完毕后才resolve结果数组出去
        resolve(result);
    }
}, function(reason){
    reject(reason);
});

只是遍历数组,把所有的异步操作都执行一遍。

这两种方法其实都有一些问题。看promise.all的实现原理就能明白。当并行的任务中有一个失败后,是直接reject的,也就是返回的promise是reject的状态。结果只也是错误的信息。

对于错误的捕获,下节再讲。

4.2.3,promise.all实现并发请求并控制数量

这个来自于常见的面试题:

实现一个并发请求函数concurrencyRequest(urls, maxNum,callback),要求如下:
• 要求最大并发数 maxNum
• 每当有一个请求返回,就留下一个空位,可以增加新的请求
• 所有请求完成后,结果按照 urls 里面的顺序依次打出,可以使用回调函数处理最后的结果
const sendRequest=(tasks,max,callBack)=>{
    let index=0;
    let limitPool=new Array(max).fill(null);
    const result=[];
    //这个map控制着并行的数量,因为map这里是同步的代码,所以异步任务进入eventLoop中执行,每次正好是这个并行数量
    const limitResult=limitPool.map(()=>{
        return new Promise((resolve)=>{
            const run=()=>{
                //因为该条并行线始终没有resolve,所以这个promsie的状态没有改变
                if(index>=tasks.length){
                    resolve()
                    return
                }
                let cur=index
                let task=tasks[index++]
                //递归,从而在当前异步完成/失败后继续执行剩下的
                task().then((res)=>{
                    result[cur]=res
                    run()
                }).catch((err)=>{
                    result[cur]=err
                    run()
                })
            }
            run()
        })
    })
    //promsie.all只会在limitResult,这几条并行的线都完成之后,才可以执行then方法,而then方法这时候就可以取最后的结果啦。
    Promise.all(limitResult).then(()=>callBack(result))
}

//开始使用
function asyncCreate(num){
    let arr=[]
    const asyncFn=function(){
        return new Promise((resolve)=>{
            setTimeout(()=>{
                console.log("代码中异步执行")
                resolve("异步完成")
            },1000)
        })
    }
    for(let i=0;i<num;i++){
        arr.push(asyncFn)
    }
    return arr
}
const funtionList=asyncCreate(10)
sendRequest(funtionList,4,res=>{
    console.log(res)
})

本质上就是使用的事件循环机制注册异步事件,换汤不换药,只不过多了个并行池的概念,利用limitPool.map来控制这个并行池中并行线的数量,而promsie.all等待的则是这个并行池的几个并行线都执行结束

另一种更简洁的方法是利用await停住代码,等并行池中有请求完成了再继续往并行池中添加请求,当最后还是利用promise.all()来保证所有请求全部完成:

async function sendRequest(tasks,limit,callback){
    const promises=[]
    const pool= new Set()
    for(const task of tasks){
        const promise=task()
        promises.push(promise)//构建结果集
        pool.add(promise)//构建并行池
        const clean=()=>pool.delete(promise)
        promise.then(clean,clean)//每个异步请求完成后,自动从并发池中清除
        if(pool.size>=limit){//并发池的并行数量等于限制数量时,使用await停住代码,直到并发池有一个请求完成
            await Promise.race(pool)
        }
    }
    Promise.all(promises).then((res)=>{callback(res)})//所有的请求完成后执行回调函数取结果
}

//开始使用
function asyncCreate(num){
    let arr=[]
    const asyncFn=function(){
        return new Promise((resolve)=>{
            setTimeout(()=>{
                console.log("代码中异步执行")
                resolve("异步完成")
            },1000)
        })
    }
    for(let i=0;i<num;i++){
        arr.push(asyncFn)
    }
    return arr
}
const funtionList=asyncCreate(10)
sendRequest(funtionList,4,res=>{
    console.log(res)
})

五,try……catch的错误捕获

5.1,promsie的同步异常捕获

上文一直没说到异常的捕获,当我们使用promsie的时候,,如果一个 Promise 操作发生了异常,那么它将会被拒绝,此时它的状态会变成 rejected。你可以使用then方法或者catch处理错误。

function asyncFunc() {
  return new Promise((resolve, reject) => {
      reject('Error from asyncFunc');
  });
}
const fn=res=>console.log("---",res)
asyncFunc().then(null,fn);   //--- Error from asyncFunc

或者采用catch:

function asyncFunc() {
  return new Promise((resolve, reject) => {
      reject('Error from asyncFunc');
  });
}
const fn=res=>console.log("---",res)
asyncFunc().catch(fn) //--- Error from asyncFunc

这是因为catch实际上是处理状态变成rejected的promise,其调用的是then的onRejected方法。所以catch也是微任务

MyPromise.prototype.catch = function(onRejected) {
  this.then(null, onRejected);
}

另外,在promise中直接throw new Error也是能被catch捕获到的:

function asyncFunc() {
  return new Promise((resolve, reject) => {
      throw new Error('Error from asyncFunc');
  });
}

asyncFunc().catch((err) => {
  console.error('Caught error:', err.message);//Caught error: Error from asyncFunc
});

但是按照刚刚的说法,catch不是要等promise的状态变成reject之后,才能使用catch捕获到错误吗?那这里为啥又能捕获到?

这是因为promise内部执行函数(executor)执行的时候,是这样封装的(具体可以看我这篇文章:https://juejin.cn/post/7218178695679410231#heading-21):

function MyPromise(fn) {
    ...//其他代码
    try {
        fn(resolve, reject);
    } catch (error) {
        reject(error);
    }
}

当在promsie中直接throw new Error的时候,会被catch捕获到,从而执行reject方法,将promise的状态修改为rejected,继而能被catch所捕获。

其实再深一层,于 JavaScript 异常处理机制:当 throw 语句抛出异常时,JavaScript 引擎会在当前作用域中查找能够处理这个异常的代码,如果找到了 try 块内的 catch 块,那么就会进入 catch 代码块去处理这个异常;否则会将异常抛给上一层作用域,直到全局作用域。

5.2,promsie的异步异常捕获

上文的throw new Error如果修改为如下代码,将其放到一个异步操作中执行:

function asyncFunc() {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      throw new Error('Error from asyncFunc');
    },1000)
  });
}
const fn=err=>console.error('Caught error:', err.message);
asyncFunc().catch(fn);

会发现又无法捕获到这个错误,也就是没有执行console.error('Caught error:', err.message);这行代码。

这又是为什么呢?

这得从事件循环机制和promise的原理(具体可以看我这篇文章:https://juejin.cn/post/7218178695679410231#heading-21)说起。

1,首先promise执行函数(executor)执行,遇到setTimeout是宏任务,进入宏任务队列。
2,执行asyncFunc().catch方法,上文说过,它内部调用的是promise的then方法,是个微任务,进入微任务队列
3,这个微任务先执行完成,这个catch实际上做的事情就是将它的回调函数fn封装下再push到promise的回调函数收集器里面等待执行。
4,这时候,宏任务setTimeout执行完毕,开始执行它的回调:throw new Error,它是无法被promise的执行函数(executor)的try……catch捕获的。(这个下文会讲到)。于是这个promsie的状态始终是pedding,也就自然不会执行收集器里面的回调函数,不会执行console.error('Caught error:', err.message)。

那promise的异常捕获应该如何处理呢?

实际上文已经说了。就是异步的错误使用reject来 包裹,这样处理的原理是使用闭包,利用的是promise执行reject会变更状态,同时取出收集器中的回调函数依次执行。如下代码:

function asyncFunc() {
    return new Promise((resolve, reject) => {
      setTimeout(()=>{
        reject(new Error('Error from asyncFunc'));
      },1000)
    });
}
const fn=err=>console.error('Caught error:', err.message);
asyncFunc().catch(fn);//Caught error: Error from asyncFunc
5.3,try…catch不能捕获的错误类型

上文其实已经遇到了,有些错误是try…catch无法捕获到的,对于上文的异步操作我们利用了promise的catch来处理。那具体都有哪些错误是try…catch无法捕获的呢?

一句话总结就是:能捕捉到的异常必须是线程执行已经进入 try catch 但 try catch 未执行完的时候抛出来的

5.3.1 直接的语法错误不能被捕获

因为语法错误是在语法检查阶段就报错了,线程执行尚未进入 try catch 代码块,自然就无法捕获到异常。

try{
  a.b
}catch(err){
  console.log(err)
}
//ReferenceError: a is not defined
5.3.2 异步无法捕获
try{
    setTimeout(()=>{
         console.log(a.b);  
    }, 100)
}catch(e){
    console.log('error',e);
}
console.log(111);
//output
111
Uncaught ReferenceError: a is not defined

因为,setTimeout是异步函数,而try catch其实是同步顺序执行的代码,等setTimeout里面的事件进入事件队列的时候,主线程已经离开了try catch,所以try catch是无法捕获异步函数的错误的。

5.3.3 多层try…catch,如果内层捕获到的错误未上抛,则上层无法捕获

多层 try-catch 时,会被最内层的 catch()方法捕获到,然后就不再向外层冒泡:

try {
  try {
    throw new Error('error');
  } catch (err) {
    console.error('内层的catch', err); // 内层的catch Error: error
  }
} catch (err) {
  console.error('最外层的catch', error);
}
5.3.4 promsie对象包裹的错误无法捕获

try catch无法捕获promise对象的错误。

function asyncFn(){
  return new Promise((resolve,reject)=>{
    throw new Error("错误了哈")
  })
}
function test(){
  try{
    asyncFn()  
  }catch(err){
    console.log("成功捕获到错误了",err)
  }
}
test()// UnhandledPromiseRejectionWarning: Error: 错误了哈

这是因为promise的执行函数(executor)执行的时候,是使用try…catch包裹的,这个上文5.1最后部分也说过了,它是catch到报错之后再reject(err),并没有把错误往上层抛,结合5.3.3来看,promise实际上并没有把错误往上抛,所以外层的try…catch无法捕获到这个错误。

接下来再来看5.2中的promise中的异步错误:

function asyncFunc() {
  return new Promise((resolve, reject) => {
    setTimeout(()=>{
      throw new Error('Error from asyncFunc');
    },1000)
  });
}
const fn=err=>console.error('Caught error:', err.message);
asyncFunc().catch(fn);

因为setTimeout在执行回调时,主线程早已经离开了promise内置的try…catch,所以并没有被它所捕获,promise的状态也就没有发生变更,更不会执行收集器中then的回调函数。

5.4 异步操作的错误捕获写法总结

为了能够捕获到异步操作的错误,总结起来,就是如下写:

5.4.1 仅使用pramise.catch时,手动reject错误
function asyncFunc() {
    return new Promise((resolve, reject) => {
      setTimeout(()=>{
        reject(new Error('Error from asyncFunc'));
      },1000)
    });
}
const fn=err=>console.error('Caught error:', err.message);
asyncFunc().catch(fn);//Caught error: Error from asyncFunc
5.4.2 使用try…catch时,需要await

await起到的作用就是主线程停留在try…catch中,从而捕获错误。

function asyncFn(){
  return new Promise((resolve,reject)=>{
    throw new Error("错误了哈")
  })
}
async function test(){
  try{
    await asyncFn()  
  }catch(err){
    console.log("成功捕获到错误了",err)
  }
}
test()//成功捕获到错误了 Error: 错误了哈

这里有个地方需要和上文联系。之前我们说过promise内部执行构造函数的时候,是如下代码:

function MyPromise(fn) {
    ...//其他代码
    try {
        fn(resolve, reject);
    } catch (error) {
        reject(error);
    }
}

这里使用了内层try…catch捕获了错误,返回的应该是reject状态的promise,并没有把错误往上层抛,理应外层的try…catch无法捕获这个错误。那现在为啥又可以捕获呢?这是因为await的效果:如果它后面是rejected状态的promise,await 表达式也会抛出错误文章来源地址https://www.toymoban.com/news/detail-434131.html

到了这里,关于浅谈异步编程中错误的捕获的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python异步网络编程利器——详解aiohttp的使用教程

    在现代Web应用程序开发中,网络请求是非常常见的操作。然而,传统的同步网络请求方式在处理大量请求时会导致性能瓶颈。为了解决这个问题,Python提供了aiohttp库,它是一个基于异步IO的网络请求库,可以实现高效的并发网络请求。本文将详细介绍aiohttp的各种使用方法,帮

    2024年02月06日
    浏览(53)
  • CompletableFuture异步编程事务及多数据源配置详解(含gitee源码)

    仓库地址: buxingzhe: 一个多数据源和多线程事务练习项目 小伙伴们在日常编码中经常为了提高程序运行效率采用多线程编程,在不涉及事务的情况下,使用dou.lea大神提供的CompletableFuture异步编程利器,它提供了许多优雅的api,我们可以很方便的进行异步多线程编程,速度杠杠

    2024年01月22日
    浏览(43)
  • C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

    本文主要介绍回调函数的使用,包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。 🎬个人简介:一个全栈工程师的升级之路! 📋个人专栏:C/C++精进之路 🎀CSDN主页 发狂的小花 🌄人生秘诀:学习的本质就是极致

    2024年02月03日
    浏览(39)
  • 【前端】浅谈async/await异步传染性

    \\\"异步传染性\\\"问题通常是指,当一个函数使用了async和await,其调用者也需要使用async和await处理异步操作,导致整个调用链都变成异步的。这种情况可能导致代码变得更复杂,不易维护。 类似于C# try catch的层层上抛,在某一层catch 查了很多资料 ,对于这个问题说法还都不一样

    2024年01月23日
    浏览(45)
  • 在React项目是如何捕获错误的?

    错误在我们日常编写代码是非常常见的 举个例子,在react项目中去编写组件内JavaScript代码错误会导致 React 的内部状态被破坏,导致整个应用崩溃,这是不应该出现的现象 作为一个框架,react也有自身对于错误的处理的解决方案 为了解决出现的错误导致整个应用崩溃的问题,

    2024年02月11日
    浏览(38)
  • php捕获Fatal error错误与异常处理

    在php5的版本中,如果出现致命错误是无法被 try {} catch 捕获的,如下所示: 运行脚本,最终php报出一个Fatal error,并程序中止 有些时候,我们需要捕获这种错误,并做相应的处理。 那就需要用到 register_shutdown_function() 和 error_get_last() 来捕获错误 对于php7中的错误捕获,因为

    2024年02月19日
    浏览(59)
  • 【Python编程错误:‘utf-8‘编解码器无法解码字节0xd5】--解决方法详解

    【Python编程错误:\\\'utf-8’编解码器无法解码字节0xd5】–解决方法详解 Python是一门非常流行的高级编程语言,用户可以很方便地使用它来实现各种功能。然而,在使用Python编写代码时,有时会遇到各种错误。本文将详细介绍一种常见的Python编程错误——\\\'utf-8’编解码器无法解

    2024年02月08日
    浏览(38)
  • 【蓝桥杯】【嵌入式组别】第十三节:PWM输入捕获编程

    目的就是测量输入到特定管脚上的PWM波的频率和占空比。 下面是PWM部分的电路图: PWM由XL555芯片产生,由滑动变阻器R40连接到PA15,滑动变阻器不同的阻值对应不同的PWM波的频率。下面一个也是一样的原理。 可以看到板子上的PA15引脚的功能分别有:TIM2_CH1和TIM8_CH1,我们在板

    2023年04月11日
    浏览(104)
  • 爬虫异常捕获与处理方法详解

    Hey!作为一名专业的爬虫代理供应商,我今天要和大家分享一些关于爬虫异常捕获与处理的方法。在进行爬虫操作时,我们经常会遇到各种异常情况,例如网络连接错误、请求超时、数据解析错误等等。这些异常情况可能会导致程序崩溃或数据丢失,因此,我们需要学会如何

    2024年02月11日
    浏览(46)
  • 【雕爷学编程】Arduino智能家居之ESP32-CAM运动检测和图像捕获

    Arduino是一个开放源码的电子原型平台,它可以让你用简单的硬件和软件来创建各种互动的项目。Arduino的核心是一个微控制器板,它可以通过一系列的引脚来连接各种传感器、执行器、显示器等外部设备。Arduino的编程是基于C/C++语言的,你可以使用Arduino IDE(集成开发环境)来

    2024年03月26日
    浏览(52)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包