Asíncrono consiste en aumentar la tasa de ocupación de la CPU y mantenerla ocupada todo el tiempo.
Algunas operaciones (la más típica es la E/S) no requieren la participación de la CPU y consumen mucho tiempo. Si no se utiliza asíncrono, se formará un estado de bloqueo, lo que provocará que la CPU esté inactiva y la página se congele.
Cuando se produce una operación de E/S en un entorno asíncrono, la CPU deja de lado el trabajo de E/S (en este momento, otros controladores se hacen cargo de la E/S y los datos aún se están transmitiendo) y luego procesa la siguiente tarea. , esperando que se complete la operación de E/S Notifique a la CPU (la devolución de llamada es un método de notificación) para que vuelva a funcionar.
El contenido principal de "JavaScript asincrónico y devolución de llamada" es que la hora de finalización específica del trabajo asincrónico es incierta. Para realizar con precisión el procesamiento posterior después de que se completa el trabajo asincrónico, se debe pasar una devolución de llamada a la función asincrónica, de modo que después. Completando su trabajo continúe con las siguientes tareas.
Aunque las devoluciones de llamadas pueden ser muy sencillas de implementar de forma asincrónica, pueden formar un infierno de devoluciones de llamadas debido al anidamiento múltiple. Para evitar el infierno de las devoluciones de llamadas, es necesario anidar y cambiar la programación anidada a programación lineal.
Promise
es la mejor solución para manejar el infierno de devoluciones de llamadas en JavaScript
.
Promise
se puede traducir como "promesa". Podemos encapsular el trabajo asincrónico y llamarlo Promise
, es decir, hacer una promesa y prometer dar una señal clara después de que finalice el trabajo asincrónico.
Sintaxis Promise
:
let promesa = nueva promesa (función (resolver, rechazar) { // Trabajo asincrónico})
A través de la sintaxis anterior, podemos encapsular el trabajo asincrónico en una Promise
. La función pasada al crear Promise
es el método para manejar el trabajo asincrónico, también conocido como executor
.
resolve
y reject
son funciones de devolución de llamada proporcionadas por el propio JavaScript
. Se pueden llamar cuando executor
completa la tarea:
resolve(result)
: si se completa con éxito, se devolverá result
reject(error)
: si la ejecución falla y se genera error
. se generará error
.executor
se ejecutará automáticamente inmediatamente después de que se cree Promise
, y su estado de ejecución cambiará el estado de las propiedades internas de Promise
:
state
: inicialmente pending
, luego se convierte en fulfilled
después de que se llama a resolve
o se rejected
cuando se llama reject
;undefined
result
luego se convierte en value
después de llamar resolve(value)
, o se convierte en error
después de llamar reject
fs.readFile
una función asincrónica; Podemos pasarlo en executor
. Las operaciones de lectura de archivos se realizan en el archivo, encapsulando así el trabajo asincrónico.
El siguiente código encapsula la función fs.readFile
y utiliza resolve(data)
para manejar resultados exitosos y reject(err)
para manejar resultados fallidos.
El código es el siguiente:
let promesa = nueva Promesa((resolver, rechazar) => { fs.readFile('1.txt', (err, datos) => { console.log('Leer 1.txt') si (err) rechazar (err) resolver (datos) })})
Si ejecutamos este código, se generarán las palabras "Leer 1.txt", lo que demuestra que la operación de lectura del archivo se realiza inmediatamente después de que se crea Promise
.
Promise
generalmente encapsula código asincrónico internamente, pero no solo encapsula código asincrónico.
El caso de Promise
anterior encapsula la operación de lectura del archivo. El archivo se leerá inmediatamente después de que se complete la creación. Si desea obtener el resultado de la ejecución Promise
, debe utilizar tres métodos then
, catch
y finally
.
El método then
de Promise
se puede utilizar para manejar el trabajo una vez completada Promise
. Recibe dos parámetros de devolución de llamada. La sintaxis es la siguiente:
promesa.entonces (función (resultado), función (error))
result
del parámetro es el valor recibido por resolve
;error
es el parámetro recibido por reject
,por ejemplo:
let;
promesa = nueva Promesa((resolver, rechazar) => { fs.readFile('1.txt', (err, datos) => { console.log('Leer 1.txt') si (err) rechazar (err) resolver (datos) })})promesa.entonces( (datos) => { console.log('Ejecutado exitosamente, el resultado es' + data.toString()) }, (errar) => { console.log('Error de ejecución, el error es' + err.message) })
Si la lectura del archivo se ejecuta exitosamente, se llamará a la primera función:
PS E:CodeNodedemos 3-callback> node .index.js Leer 1.txt Si se ejecuta correctamente, el resultado es 1
1.txt
eliminado. Si la ejecución falla, se llamará a la segunda función:
PS E:CodeNodedemos 3-callback> node .index.js. Leer 1.txt La ejecución falló con el error ENOENT: no existe tal archivo o directorio, abra 'E:CodeNodedemos 3-callback1.txt'
Si solo nos centramos en el resultado de una ejecución exitosa, solo podemos pasar uno función de devolución de llamada:
promesa .then((data)=>{ console.log('Ejecutado con éxito, el resultado es' + data.toString())})
En este punto, hemos implementado una operación de lectura asincrónica del archivo.
Si solo nos centramos en el resultado del error, podemos pasar null
al primero then
devolver la llamada: promise.then(null,(err)=>{...})
.
O utilice una forma más elegante: promise.catch((err)=>{...})
let promesa = nueva Promesa((resolve, rechazar) => { fs.readFile('1.txt', (err, datos) => { console.log('Leer 1.txt') si (err) rechazar (err) resolver (datos) })})promesa.catch((err)=>{ console.log(err.message)})
.catch((err)=>{...})
y then(null,(err)=>{...})
tienen exactamente el mismo efecto.
.finally
es una función que se ejecutará independientemente del resultado de promise
. Tiene el mismo propósito que finally
en la sintaxis try...catch...
y puede manejar operaciones no relacionadas con el resultado.
Por ejemplo:
nueva Promesa((resolver,rechazar)=>{ //algo...}).finally(()=>{console.log('Ejecutar independientemente del resultado')}).then(result=>{...}, err=>{...} )La devolución de llamada finalmente no tiene parámetros
fs.readFile()
lee 10 archivos secuencialmente y genera el contenido. de los diez archivos secuencialmente.
Dado que fs.readFile()
en sí es asíncrono, debemos utilizar el anidamiento de devolución de llamada. El código es el siguiente:
fs.readFile('1.txt', (err, data) => { console.log(data.toString()) //1 fs.readFile('2.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('3.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('4.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('5.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('6.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('7.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('8.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('9.txt', (err, datos) => { consola.log(data.toString()) fs.readFile('10.txt', (err, datos) => { consola.log(data.toString()) // ==> Puerta del Infierno}) }) }) }) }) }) }) }) })})
Aunque el código anterior puede completar la tarea, a medida que aumenta el anidamiento de llamadas, el nivel del código se vuelve más profundo y la dificultad de mantenimiento aumenta, especialmente cuando usamos código real que puede contener muchos bucles y declaraciones condicionales, en lugar de. simple console.log(...)
en el ejemplo.
Si no usamos devoluciones de llamada y llamamos directamente a fs.readFile()
en secuencia de acuerdo con el siguiente código, ¿qué pasará?
//Nota: esta es la forma incorrecta de escribir fs.readFile('1.txt', (err, data) => { console.log(data.toString())})fs.readFile('2.txt', (err, datos) => { console.log(data.toString())})fs.readFile('3.txt', (err, datos) => { console.log(data.toString())})fs.readFile('4.txt', (err, datos) => { console.log(data.toString())})fs.readFile('5.txt', (err, datos) => { console.log(data.toString())})fs.readFile('6.txt', (err, datos) => { console.log(data.toString())})fs.readFile('7.txt', (err, datos) => { console.log(data.toString())})fs.readFile('8.txt', (err, datos) => { console.log(data.toString())})fs.readFile('9.txt', (err, datos) => { console.log(data.toString())})fs.readFile('10.txt', (err, datos) => { console.log(data.toString())})
Los siguientes son los resultados de mi prueba (los resultados de cada ejecución son diferentes):
PS E:CodeNodedemos 3-callback> node .index.La razón por la que
js12346957108
produce este resultado no secuencial es asíncrono y no se puede lograr un paralelismo de subprocesos múltiples en un solo subproceso.
La razón por la que se utiliza este caso de error aquí es para enfatizar el concepto de asincronía. Si no comprende por qué ocurre este resultado, debe regresar y recuperar la lección.
La idea de usar Promise
para resolver la lectura secuencial asincrónica de archivos:
promise1
y use resolve
para devolver el resultado.promise1.then
para recibir y generar el resultado de la lectura del archivopromise2
en promise1.then
y vuelvapromise2.then
para recibir y generar el resultado de la lecturapromise3
en promise2.then
y vuelva apromise3.then
para recibir y generar el resultado de la... El código es el siguiente:
let promesa1 = nueva promesa ((resolver, rechazar) => { fs.readFile('1.txt', (err, datos) => { si (err) rechazar (err) resolver (datos) })})dejar promesa2 = promesa1.entonces( datos => { consola.log(data.toString()) devolver nueva Promesa((resolver, rechazar) => { fs.readFile('2.txt', (err, datos) => { si (err) rechazar (err) resolver (datos) }) }) }) dejar promesa3 = promesa2.entonces( datos => { consola.log(data.toString()) devolver nueva Promesa((resolver, rechazar) => { fs.readFile('3.txt', (err, datos) => { si (err) rechazar (err) resolver (datos) }) }) }) dejar promesa4 = promesa3.entonces( datos => { consola.log(data.toString()) //...... })... ...
De esta manera escribimos el infierno de devolución de llamada anidado original en un modo lineal.
Pero todavía hay un problema con el código. Aunque el código se ha vuelto más hermoso en términos de administración, aumenta enormemente su longitud.
El código anterior es demasiado largo. Podemos reducir la cantidad de código mediante dos pasos:
promise
intermedia y vincular el .then
código de la siguiente manera:
función myReadFile (ruta) { devolver nueva Promesa((resolver, rechazar) => { fs.readFile(ruta, (err, datos) => { si (err) rechazar (err) consola.log(data.toString()) resolver() }) })}myReadFile('1.txt') .entonces(datos => {return myReadFile('2.txt') }) .entonces(datos => {return myReadFile('3.txt') }) .entonces(datos => { return myReadFile('4.txt') }) .entonces(datos => {return myReadFile('5.txt') }) .entonces(datos => {return myReadFile('6.txt') }) .entonces(datos => {return myReadFile('7.txt') }) .entonces(datos => {return myReadFile('8.txt') }) .entonces(datos => { return myReadFile('9.txt') }) .then(data => { return myReadFile('10.txt') })
Dado que el método myReadFile
devolverá una nueva Promise
, podemos ejecutar directamente el método .then
. Este método de programación se llama programación en cadena .
El resultado de la ejecución del código es el siguiente:
PS E:CodeNodedemos 3-callback> node .index.js12345678910
Esto completa la operación de lectura de archivos asíncrona y secuencial.
Nota: Se debe devolver un nuevo objeto
Promise
en el método.then
de cada paso; de lo contrario, se recibirá laPromise
anterior.Esto se debe a que cada método
then
continuará transmitiendo suPromise
hacia abajo.