javascript - sincronas - promesas typescript
¿Cómo envolver las llamadas de función asíncrona en una función de sincronización en Node.js o Javascript? (10)
Supongamos que mantiene una biblioteca que expone una función getData
. Sus usuarios lo llaman para obtener datos reales:
var output = getData();
Debajo de la capucha, los datos se guardan en un archivo, por lo que implementó getData
utilizando Node.js incorporado fs.readFileSync
. Es obvio que tanto getData
como fs.readFileSync
son funciones de sincronización. Un día se le dijo que cambie la fuente de datos subyacente a un repositorio como MongoDB al que solo se puede acceder de forma asincrónica. También se le dijo que evite molestar a sus usuarios, getData
API no se puede cambiar para devolver simplemente una promesa o exigir un parámetro de devolución de llamada. ¿Cómo cumple ambos requisitos?
La función asincrónica que utiliza devolución de llamada / promesa es el ADN de JavasSript y Node.js. Cualquier aplicación JS no trivial probablemente esté impregnada de este estilo de codificación. Pero esta práctica puede conducir fácilmente a la llamada pirámide de devolución de doom. Peor aún, si cualquier código de cualquier persona que llama en la cadena de llamadas depende del resultado de la función asíncrona, ese código también debe estar envuelto en la función de devolución de llamada, imponiendo una restricción de estilo de codificación a la persona que llama. De vez en cuando, encuentro la necesidad de encapsular una función asíncrona (que a menudo se proporciona en una biblioteca de terceros) en una función de sincronización para evitar el refaccionamiento global masivo. La búsqueda de una solución sobre este tema usualmente terminaba con Node Fibers o paquetes npm derivados de ella. Pero Fibras simplemente no puede resolver el problema que estoy enfrentando. Incluso el ejemplo proporcionado por el autor de Fibers ilustró la deficiencia:
...
Fiber(function() {
console.log(''wait... '' + new Date);
sleep(1000);
console.log(''ok... '' + new Date);
}).run();
console.log(''back in main'');
Salida real:
wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
back in main
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
Si la función Fiber realmente convierte la función asincrónica en sincronización, el resultado debería ser:
wait... Fri Jan 21 2011 22:42:04 GMT+0900 (JST)
ok... Fri Jan 21 2011 22:42:05 GMT+0900 (JST)
back in main
He creado otro ejemplo simple en JSFiddle y JSFiddle buscando un código para obtener el resultado esperado. Aceptaré una solución que solo funciona en Node.js, por lo que puede solicitar cualquier paquete npm a pesar de no estar trabajando en JSFiddle.
Si la función Fiber realmente convierte la función de sincronización de sueño en sincronización
Sí. Dentro de la fibra, la función espera antes de iniciar sesión. Las fibras no hacen que las funciones asincrónicas sean síncronas, sino que permiten escribir código de aspecto síncrono que usa funciones asíncronas y luego se ejecutarán de forma asincrónica dentro de una Fiber
.
De vez en cuando, encuentro la necesidad de encapsular una función asíncrona en una función de sincronización para evitar una refaccionamiento global masivo.
No puedes. Es imposible hacer que el código asíncrono sea síncrono. Deberá anticiparlo en su código global y escribirlo en estilo asíncrono desde el principio. Ya sea que envuelva el código global en una fibra, use promesas, generadores prometedores o devoluciones de llamada simples dependiendo de sus preferencias.
Mi objetivo es minimizar el impacto en la persona que llama cuando el método de adquisición de datos cambia de sincronización a sincronización
Ambas promesas y fibras pueden hacer eso.
Hacer la sincronización del código Node.js es esencial en algunos aspectos, como la base de datos. Pero la ventaja real de Node.js radica en el código asíncrono. Como es un hilo único sin bloqueo.
podemos sincronizarlo usando una funcionalidad importante Fiber () Use await () y defer () llamamos a todos los métodos usando await (). luego reemplace las funciones de devolución de llamada con defer ().
Código asincrónico normal. Esto usa las funciones CallBack.
function add (var a, var b, function(err,res){
console.log(res);
});
function sub (var res2, var b, function(err,res1){
console.log(res);
});
function div (var res2, var b, function(err,res3){
console.log(res3);
});
Sincronice el código anterior con Fiber (), await () y defer ()
fiber(function(){
var obj1 = await(function add(var a, var b,defer()));
var obj2 = await(function sub(var obj1, var b, defer()));
var obj3 = await(function sub(var obj2, var b, defer()));
});
Espero que esto sea de ayuda. Gracias
Hoy en día, este patrón de generador puede ser una solución en muchas situaciones.
Aquí un ejemplo de solicitudes de consola secuenciales en nodejs que utilizan la función async readline.question:
var main = (function* () {
// just import and initialize ''readline'' in nodejs
var r = require(''readline'')
var rl = r.createInterface({input: process.stdin, output: process.stdout })
// magic here, the callback is the iterator.next
var answerA = yield rl.question(''do you want this? '', r=>main.next(r))
// and again, in a sync fashion
var answerB = yield rl.question(''are you sure? '', r=>main.next(r))
// readline boilerplate
rl.close()
console.log(answerA, answerB)
})() // <-- executed: iterator created from generator
main.next() // kick off the iterator,
// runs until the first ''yield'', including rightmost code
// and waits until another main.next() happens
Javascript es un lenguaje de un solo hilo, ¡no quieres bloquear todo tu servidor! El código asíncrono elimina las condiciones de carrera al hacer las dependencias explícitas.
¡Aprende a amar el código asíncrono!
Eche un vistazo a las promises
para el código asincrónico sin crear una pirámide de retrollamada. Recomiendo la biblioteca promiseQ para node.js
httpGet(url.parse("http://example.org/")).then(function (res) {
console.log(res.statusCode); // maybe 302
return httpGet(url.parse(res.headers["location"]));
}).then(function (res) {
console.log(res.statusCode); // maybe 200
});
EDITAR: esta es, de lejos, mi respuesta más polémica, el nodo ahora tiene la palabra clave yield, que le permite tratar el código asíncrono como si fuera sincronizado. http://blog.alexmaccaw.com/how-yield-will-transform-node
Luché con esto al principio con node.js y async.js es la mejor biblioteca que he encontrado para ayudarte a lidiar con esto. Si desea escribir un código sincrónico con un nodo, el enfoque es de esta manera.
var async = require(''async'');
console.log(''in main'');
doABunchOfThings(function() {
console.log(''back in main'');
});
function doABunchOfThings(fnCallback) {
async.series([
function(callback) {
console.log(''step 1'');
callback();
},
function(callback) {
setTimeout(callback, 1000);
},
function(callback) {
console.log(''step 2'');
callback();
},
function(callback) {
setTimeout(callback, 2000);
},
function(callback) {
console.log(''step 3'');
callback();
},
], function(err, results) {
console.log(''done with things'');
fnCallback();
});
}
este programa SIEMPRE producirá lo siguiente ...
in main
step 1
step 2
step 3
done with things
back in main
No debería estar mirando lo que sucede alrededor de la llamada que crea la fibra sino más bien lo que sucede dentro de la fibra. Una vez dentro de la fibra, puede programar en estilo sincronizado. Por ejemplo:
function f1() { console.log(''wait... '' + new Date); sleep(1000); console.log(''ok... '' + new Date); } function f2() { f1(); f1(); } Fiber(function() { f2(); }).run();
Dentro de la fibra, llamas a f1
, f2
y sleep
como si estuvieran sincronizados.
En una aplicación web típica, creará Fiber en su despachador de solicitudes HTTP. Una vez hecho esto, puede escribir toda su lógica de manejo de solicitudes en el estilo de sincronización, incluso si llama a las funciones asíncronas (fs, bases de datos, etc.).
No puedo encontrar un escenario que no pueda ser resuelto usando fibras de nodo. El ejemplo que proporcionó utilizando fibras de nodo se comporta como se esperaba. La clave es ejecutar todo el código relevante dentro de una fibra, por lo que no tiene que comenzar una nueva fibra en posiciones aleatorias.
Veamos un ejemplo: Digamos que usa algún framework, que es el punto de entrada de su aplicación (no puede modificar este framework). Este marco carga módulos nodejs como complementos y llama a algunos métodos en los complementos. Digamos que este marco solo acepta funciones sincrónicas, y no usa fibras por sí mismo.
Hay una biblioteca que desea usar en uno de sus complementos, pero esta biblioteca es asíncrona y tampoco desea modificarla.
El hilo principal no se puede producir cuando no se está ejecutando fibra, ¡pero aún se pueden crear complementos utilizando fibras! Simplemente crea una entrada envoltura que comience todo el marco dentro de una fibra, para que puedas ceder la ejecución desde los complementos.
Desventaja: si el framework usa setTimeout
o Promise
s internamente, escapará del contexto de fibra. Esto puede setTimeout
burlándose de setTimeout
, Promise.then
y todos los controladores de eventos.
Entonces, así es como puedes ceder una fibra hasta que se resuelva una Promise
. Este código toma una función asincrónica (Promesa que regresa) y reanuda la fibra cuando se resuelve la promesa:
framework-entry.js
console.log(require("./my-plugin").run());
async-lib.js
exports.getValueAsync = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve("Async Value");
}, 100);
});
};
my-plugin.js
const Fiber = require("fibers");
function fiberWaitFor(promiseOrValue) {
var fiber = Fiber.current, error, value;
Promise.resolve(promiseOrValue).then(v => {
error = false;
value = v;
fiber.run();
}, e => {
error = true;
value = e;
fiber.run();
});
Fiber.yield();
if (error) {
throw value;
} else {
return value;
}
}
const asyncLib = require("./async-lib");
exports.run = () => {
return fiberWaitFor(asyncLib.getValueAsync());
};
my-entry.js
require("fibers")(() => {
require("./framework-entry");
}).run();
Cuando ejecute node framework-entry.js
lanzará un error: Error: yield() called with no fiber running
. Si ejecuta el node my-entry.js
, funciona como se esperaba.
También hay un módulo de sincronización npm. que se utiliza para sincronizar el proceso de ejecución de la consulta.
Cuando desee ejecutar consultas paralelas de manera síncrona, restrinja el nodo para hacer eso porque nunca espera la respuesta. y el módulo de sincronización es perfecto para ese tipo de solución.
Código de muestra
/*require sync module*/
var Sync = require(''sync'');
app.get(''/'',function(req,res,next){
story.find().exec(function(err,data){
var sync_function_data = find_user.sync(null, {name: "sanjeev"});
res.send({story:data,user:sync_function_data});
});
});
/*****sync function defined here *******/
function find_user(req_json, callback) {
process.nextTick(function () {
users.find(req_json,function (err,data)
{
if (!err) {
callback(null, data);
} else {
callback(null, err);
}
});
});
}
enlace de referencia: https://www.npmjs.com/package/sync
Tienes que usar promesas:
const asyncOperation = () => {
return new Promise((resolve, reject) => {
setTimeout(()=>{resolve("hi")}, 3000)
})
}
const asyncFunction = async () => {
return await asyncOperation();
}
const topDog = () => {
asyncFunction().then((res) => {
console.log(res);
});
}
Me gustan más las definiciones de la función de flecha. Pero cualquier cadena de la forma "() => {...}" también podría escribirse como "función () {...}"
Entonces topDog no es asincrónico a pesar de llamar a una función asíncrona.
EDITAR: Me doy cuenta de que muchas de las veces que necesita para envolver una función de sincronización dentro de una función de sincronización se encuentra dentro de un controlador. Para esas situaciones, aquí hay un truco de fiesta:
const getDemSweetDataz = (req, res) => {
(async () => {
try{
res.status(200).json(
await asyncOperation()
);
}
catch(e){
res.status(500).json(serviceResponse); //or whatever
}
})() //So we defined and immediately called this async function.
}
Utilizando esto con devoluciones de llamada, puede hacer una envoltura que no use promesas:
const asyncOperation = () => {
return new Promise((resolve, reject) => {
setTimeout(()=>{resolve("hi")}, 3000)
})
}
const asyncFunction = async (callback) => {
let res = await asyncOperation();
callback(res);
}
const topDog = () => {
let callback = (res) => {
console.log(res);
};
(async () => {
await asyncFunction(callback)
})()
}
Al aplicar este truco a un EventEmitter, puede obtener los mismos resultados. Definir el oyente de EventEmitter donde he definido la devolución de llamada, y emitir el evento donde llamé a la devolución de llamada.
deasync convierte la función asíncrona en sincronización, implementada con un mecanismo de bloqueo al invocar el bucle de eventos de Node.js en la capa de JavaScript. Como resultado, la deasincronización solo bloquea la ejecución del código subsiguiente sin bloquear el hilo completo, ni incidir en la espera ocupada. Con este módulo, esta es la respuesta al desafío jsFiddle:
function AnticipatedSyncFunction(){
var ret;
setTimeout(function(){
ret = "hello";
},3000);
while(ret === undefined) {
require(''deasync'').runLoopOnce();
}
return ret;
}
var output = AnticipatedSyncFunction();
//expected: output=hello (after waiting for 3 sec)
console.log("output="+output);
//actual: output=hello (after waiting for 3 sec)
(descargo de responsabilidad: soy el coautor de deasync
. El módulo se creó después de publicar esta pregunta y no se encontró una propuesta viable).