Hablando de la captura de errores en la programación asíncrona

Explicación detallada del escenario de programación asíncrona de awai

Como se mencionó en el artículo anterior, async y await pueden reemplazar la combinación de la función de generador y el rendimiento para realizar operaciones asíncronas elegantes escritas de forma síncrona. ¿Qué pasa con la captura de errores asíncronos? En este artículo, primero hablaré sobre las características de async y await, y luego explicaré la captura de errores en la programación asíncrona.

Uno, la palabra clave asíncrona

La función marcada con la palabra clave async se convertirá en una función asíncrona y su valor de retorno es diferente al de las funciones ordinarias.

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

1.1, el regreso de async debe ser una promesa

Si devuelve un valor simple, lo convierte en una promesa usando promise.resolve().

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

Si el valor devuelto es una promesa, siga la lógica de la promesa normal para ver si el resultado de su operación asíncrona tiene éxito o falla.

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, devuelve un objeto entoncesable

Si el valor devuelto es un objeto thenable, porque el método then en él se ejecutará recursivamente, la promesa finalmente devuelta es la promesa procesada en el método then del objeto thenable.

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对象
})

Dos, la palabra clave espera

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

await solo se puede usar en funciones asíncronas asíncronas y devuelve el valor de resultado de promsie. Aquí puede ver el código para leer el contenido del archivo antes:

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

Tres, la escritura síncrona de async/await solo tiene efecto en la misma pila de llamadas asíncronas

El siguiente código se imprimirá 成功了después 22222. Es porque await detiene el código. Solo tendrá efecto en la pila de llamadas de la prueba asíncrona.

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
//成功了

Cuarto, async y await permiten que las operaciones asincrónicas se pongan en cola y se completen en paralelo.

4,1, operaciones asincrónicas completas en serie

Cuando usamos async/await de forma rutinaria, el siguiente código ejecutará tareas asincrónicas en orden, y la siguiente se ejecutará después de que finalice una, lo que hará que el código en la pila de llamadas consuma mucho tiempo.

El siguiente código:

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

En la función de prueba aquí, las tres operaciones asincrónicas se completarán en orden, y la siguiente se completará después de que se complete una, lo que requiere mucho tiempo y tomará 6 segundos aquí. En algunos casos, preferimos completar solicitudes asincrónicas en paralelo para ahorrar tiempo.

4.2, Permitir que las solicitudes asincrónicas se completen en paralelo

4.2.1, utilizando el mecanismo de bucle de eventos de js

Cuando varias tareas son independientes entre sí y no tienen dependencias:

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

Cuando ejecutamos const a=test1();;const b=test2();const c= test3(), debido a que la promesa ejecuta el constructor, es sincrónico, por lo que estas tres operaciones asincrónicas se han registrado y ejecutado en la tabla de eventos, lo que se puede entender como tres teteras que se enchufan al mismo tiempo y comienzan a hervir agua. (Esta metáfora se puede entender a partir de mi artículo: mecanismo de bucle de eventos js-tarea macro micro tarea_笑道三千的博客-CSDN Blog )

En este momento, abc es una promesa cuyo estado es transitorio. El código continúa ejecutándose para await, porque el efecto de await es en realidad el mismo que yield, lo que suspenderá la ejecución del código. El uso interno es el siguiente:

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

Esta forma de procesamiento, es decir, await llama al método then y devuelve el valor del resultado de la promesa (esperando a que se cumpla el estado).

En otras palabras, las tres ollas de agua tardaron solo 3 segundos en hervir. El primer await regresó después del primer segundo, y el segundo continuó ejecutando el código, luego fue al segundo await y luego al tercero. . Esto permite la ejecución paralela de operaciones asincrónicas.

4.2.2, usando el método promise.all

Además, puede usar el método promise.all para lograr, de hecho, el principio es el mismo. Pero puede esperar a que se completen estos procesos asincrónicos paralelos antes de procesar los resultados de manera unificada.

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

De hecho, este también es el mecanismo de bucle de eventos de js, echemos un vistazo al principio de implementación de 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;
}

Puedes ver que también se usa internamente:

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

Simplemente recorra la matriz y realice todas las operaciones asincrónicas.

Ambos métodos en realidad tienen algunos problemas. Puede entenderlo observando el principio de implementación de promise.all. Cuando una de las tareas paralelas falla, se rechaza directamente, es decir, la promesa devuelta se encuentra en estado rechazado. El resultado es solo información incorrecta.

Para la captura incorrecta, hablaremos de ello en la siguiente sección.

4.2.3, promise.all implementa solicitudes simultáneas y controla el número

Este proviene de una pregunta común de la entrevista:

实现一个并发请求函数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)
})

En esencia, utiliza el mecanismo de bucle de eventos para registrar eventos asíncronos, cambiando la sopa sin cambiar la medicina, pero se agrega el concepto de un grupo paralelo.LimitPool.map se usa para controlar el número de líneas paralelas en el grupo paralelo , mientras que promsie.all waits Es la ejecución de varias líneas paralelas de este pool paralelo .

Otro método más conciso es usar await para detener el código, esperar a que se complete la solicitud en el grupo paralelo y luego continuar agregando solicitudes al grupo paralelo y, finalmente, usar promise.all() para garantizar que todas las solicitudes se completen. terminado:

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

Cinco, intente... captura captura de error

5.1, captura de excepción síncrona de promsie

Lo anterior no ha mencionado la captura de excepción, cuando usamos promsie, si ocurre una excepción en una operación Promise, será rechazada, y su estado pasará a ser rechazado. Puede usar el método then o catch para manejar los errores.

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

O usa catch:

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

Esto se debe a que la captura es en realidad una promesa cuyo estado de procesamiento se rechaza y llama al método onRejected de ese momento. Así que catch también es una microtarea .

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

Además, arrojar un nuevo Error directamente en la promesa también puede ser detectado por captura:

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
});

Pero de acuerdo con la declaración de ahora, ¿no tiene que esperar catch hasta que el estado de la promesa sea rechazado antes de usar catch para detectar el error? Entonces, ¿por qué se puede capturar aquí?

Esto se debe a que cuando se ejecuta la función de ejecución interna (ejecutor) de la promesa, se encapsula de esta manera (para más detalles, consulte mi artículo: https://juejin.cn/post/7218178695679410231#heading-21):

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

Al lanzar un nuevo error directamente en promsie, será capturado por captura, por lo que se ejecutará el método de rechazo y el estado de promesa cambiará a rechazado, que luego puede ser capturado por captura.

De hecho, un nivel más profundo es el mecanismo de manejo de excepciones de JavaScript: cuando throwla declaración lanza una excepción, el motor de JavaScript buscará código que pueda manejar la excepción en el alcance actual, y si encuentra el bloquetry dentro del bloque , ingresará el bloque de código para Manejar la excepción; de lo contrario, lance la excepción al alcance superior hasta el alcance global.catchcatch

5.2, captura de excepción asíncrona de Promsie

Si el error de lanzamiento anterior se modifica al siguiente código, colóquelo en una operación asíncrona para su ejecución:

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);

Encontrará que este error no se puede detectar nuevamente, es decir, console.error('Caught error:', err.message);esta línea de código no se ejecuta.

¿Esta es la razón por?

Esto tiene que comenzar con el principio del mecanismo de bucle de eventos y la promesa (ver mi artículo para más detalles: 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)。

¿Cómo debe manejarse la excepción de captura de promesa?

De hecho, el texto ya ha sido dicho. Es decir, los errores asíncronos son envueltos por rechazo.El principio de este procesamiento es usar cierres, usando promesas para ejecutar rechazos para cambiar el estado, y al mismo tiempo sacar las funciones de devolución de llamada en el colector y ejecutarlas a su vez. El siguiente código:

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, tipos de error que intentan... atrapar no pueden atrapar

En realidad, se ha encontrado lo anterior, y algunos errores no se pueden detectar con try... catch Para la operación asincrónica anterior, usamos la captura de promesa para manejarla. Entonces, ¿cuáles son los errores específicos que intentan... atrapar no pueden atrapar?

El resumen de una oración es: la excepción que se puede capturar debe lanzarse cuando la ejecución del subproceso ha ingresado a la captura de prueba pero la captura de prueba no se ha ejecutado.

5.3.1 No se pueden detectar errores de sintaxis directa

Debido a que el error de sintaxis se informa en la etapa de verificación de sintaxis, la ejecución del subproceso aún no ha ingresado al bloque de código de captura de prueba, por lo que, naturalmente, la excepción no se puede capturar.

try{
    
    
  a.b
}catch(err){
    
    
  console.log(err)
}
//ReferenceError: a is not defined
5.3.2 Async no puede atrapar
try{
    
    
    setTimeout(()=>{
    
    
         console.log(a.b);  
    }, 100)
}catch(e){
    
    
    console.log('error',e);
}
console.log(111);
//output
111
Uncaught ReferenceError: a is not defined

Debido a que setTimeout es una función asíncrona, y try catch es en realidad código ejecutado en un orden síncrono.Cuando los eventos en setTimeout ingresan a la cola de eventos, el subproceso principal ha dejado try catch, por lo que try catch no puede detectar errores de funciones asíncronas.

5.3.3 Intentar... capturar multicapa, si el error capturado por la capa interna no se detecta, la capa superior no puede detectarlo

Cuando se usa el método try-catch multicapa, será capturado por el método catch() más interno, y luego ya no burbujeará en la capa externa:

try {
    
    
  try {
    
    
    throw new Error('error');
  } catch (err) {
    
    
    console.error('内层的catch', err); // 内层的catch Error: error
  }
} catch (err) {
    
    
  console.error('最外层的catch', error);
}
5.3.4 No se pueden capturar los errores envueltos por objetos promsie

try catch no puede detectar el error del objeto de promesa.

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

Esto se debe a que cuando se ejecuta la función de ejecución (ejecutor) de la promesa, se envuelve con try...catch.Como se mencionó en la última parte de 5.1 anterior, rechaza (err) después de detectar el error y no envía el error al lanzamiento de nivel superior, combinado con 5.3.3, la promesa en realidad no lanza el error hacia arriba, por lo que el intento externo... atrapar no puede detectar este error.

A continuación, veamos el error asíncrono en promesa en 5.2:

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);

Porque cuando setTimeout ejecuta la devolución de llamada, el subproceso principal ya ha dejado el try... catch incorporado de la promesa, por lo que no ha sido capturado por ella, y el estado de la promesa no ha cambiado, y la función de devolución de llamada de entonces en el colector no se ejecutará.

5.4 Resumen de captura de errores para operaciones asíncronas

Para poder capturar los errores de las operaciones asíncronas, en resumen, se escribe de la siguiente manera:

5.4.1 Errores de rechazo manual cuando solo se usa promise.catch
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 Cuando se usa try...catch, se requiere esperar

La función de await es que el hilo principal permanece en try... catch para detectar errores.

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

Hay un lugar aquí que necesita estar conectado con lo anterior. Antes decíamos que cuando la promesa ejecuta internamente el constructor, es el siguiente código:

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

Aquí, el intento interno... atrapar se usa para capturar el error, y el retorno debe ser una promesa en el estado de rechazo, y el error no se arroja a la capa superior. Debería ser que el intento externo... atrapar no puede detectar este error. Entonces, ¿por qué se puede capturar ahora? Esto se debe al efecto de await: la expresión await también genera un error si va seguida de una promesa rechazada .

Supongo que te gusta

Origin blog.csdn.net/weixin_42349568/article/details/130497858
Recomendado
Clasificación