I'm trying to return a custom object from a async function that works as wrapper for a put using indexdb.
Using Promises this is easy.
However, using async/await became more challenging...
const set = async (storeName, key, value) => {
if (!db)
throw new Error("no db!");
try {
const result = {};
let tx = db.transaction(storeName, "readwrite");
let store = tx.objectStore(storeName);
let r = store.put({ data: key, value: value });
console.log(r);
r.onsuccess = async () => {
console.log('onsuccess');
result.something = true;
}
r.onerror = async () => {
console.log('onerror');
result.something = false;
}
await r.transaction.complete; // ok... this don't work
// how can I await until onsuccess or onerror runs?
return result;
} catch (error) {
console.log(error);
}
}
The ideia is to return a composed object... however all my attemps fails as onsuccess runs after returning the result.
I googled a lot and could't find a way to proper await for onsuccess/onerror events.
I know that returning a Promise is more easy as resolve(result) would end returning what I want... but i'm trying to learn to make same code using async/await.
Thank you so much,
Try this:
function set(db, storeName, key, value) {
return new Promise((resolve, reject) => {
let result;
const tx = db.transaction(storeName, 'readwrite');
tx.oncomplete = _ => resolve(result);
tx.onerror = event => reject(event.target.error);
const store = tx.objectStore(storeName);
const request = store.put({data: key, value: value});
request.onsuccess = _ => result = request.result;
});
}
async function callIt() {
const db = ...;
const result = await set(db, storeName, key, value);
console.log(result);
}
Edit, since you insist on using the async qualifier for the set function, you can do this instead. Please note I find this pretty silly:
async function set(db, storeName, key, value) {
// Wrap the code that uses indexedDB in a promise because that is
// the only way to use indexedDB together with promises and
// async/await syntax. Note this syntax is much less preferred than
// using the promise-returning function pattern I used in the previous
// section of this answer.
const promise = new Promise((resolve, reject) => {
let result;
const tx = db.transaction(storeName, 'readwrite');
tx.oncomplete = _ => resolve(result);
tx.onerror = event => reject(event.target.error);
const store = tx.objectStore(storeName);
const request = store.put({data: key, value: value});
request.onsuccess = _ => result = request.result;
});
// We have executed the promise, but have not awaited it yet. So now we
// await it. We can use try/catch here too, if we want, because the
// await will translate the promise rejection into an exception. Of course,
// this is also rather silly because we are doing the same thing as just
// allowing an uncaught exception to exit the function early.
let result;
try {
result = await promise;
} catch(error) {
console.log(error);
return;
}
// Now do something with the result
console.debug('The result is', result);
}
Ultimately you'll end up wrapping IDB in a promise-friend library, but for your specific need, you could use something like this:
function promiseForTransaction(tx) {
return new Promise((resolve, reject) => {
tx.oncomplete = e => resolve();
tx.onabort = e => reject(tx.error);
});
}
And then in your code you can write things such as:
await promiseForTransaction(r.tx);
... which will wait until the transaction completes, and throw an exception if it aborts. (Note that this requires calling the helper
before the transaction could possibly have completed/aborted, since
it won't ever resolve if the events have already fired)
I can't confirm it right now but I think it should be await tx.complete instead of await r.transaction.complete;.
But a general solution that would work even if the API would not support Promises directly would be to wrap a new Promise around the onsuccess and onerror and use await to wait for that Promise to resolve, and in your onsuccess and onerror you then call the resolve function:
const set = async (storeName, key, value) => {
if (!db)
throw new Error("no db!");
try {
const result = {};
let tx = db.transaction(storeName, "readwrite");
let store = tx.objectStore(storeName);
let r = store.put({
data: key,
value: value
});
console.log(r);
await new Promise((resolve, reject) => {
r.onsuccess = () => {
console.log('onsuccess');
result.something = true;
resolve()
}
r.onerror = () => {
console.log('onerror');
result.something = false;
// I assume you want to resolve the promise even if you get an error
resolve()
}
})
return result;
} catch (error) {
console.log(error);
}
}
I would furhter change it to:
try {
await new Promise((resolve, reject) => {
r.onsuccess = resolve
r.onerror = reject
})
console.log('success');
result.something = true;
} catch(err) {
console.log('error');
result.something = false;
}
Related
I am using a library call to connect to my vendor. The libary call requires a callback in the call. Without a callback in the function, I can easily make this synchronous. With the Callback, everything I do is stuck in the callback and never bubbles it way out.
I have literally tried 100 different ways to get this to work.
function removeFromDNC(emailAddress, accessToken_in)
{
return new Promise( function(resolve, reject)
{
try{
const options =
{
auth: {
accessToken: accessToken_in
}
, soapEndpoint: 'https://webservice.XXX.exacttarget.com/Service.asmx'
};
var co = {
"CustomerKey": "DNC",
"Keys":[
{"Key":{"Name":"Email Address","Value": emailAddress}}]
};
var uo = {
SaveOptions: [{"SaveOption":{PropertyName:"DataExtensionObject",SaveAction:"Delete"}}]
};
const soapClient = new FuelSoap(options);
//again, I don't control the structure of the next call.
let res = soapClient.delete('DataExtensionObject', co, uo, async function( err, response ) {
if ( err ) {
// I can get here, but my reject, or if I use return, does nothing
reject();
}else{
// I can get here, but my reject, or if I use return, does nothing
resolve();
}
});
console.log("res value " + res); // undefined - of course
}catch(err){
console.log("ALERT: Bad response back for removeFromDNC for email: " + emailAddress + " error: " + err);
console.log("removeFromDNC promise fulfilled in catch");
reject();
}
});
}
Both methods resolve and reject expect parameters, which are res and err in your case.
As far as removeFromDNC returns a Promise instance, you should call it using either async/await syntax:
const res = await removeFromDNC(...);
or chaining then/catch calls:
removeFromDNC(...)
.then((res) => { ... }) // resolve
.catch((err) => { ... }) // reject
EDIT:
If you want to avoid usage of callbacks inside removeFromDNC, consider promisifying of soapClient.delete call. Refer to util.promisify() if you working in Node.js or use own implementation.
Here is the example for demonstration:
const promisify = (fun) => (...args) => {
return new Promise((resolve, reject) => {
fun(...args, (err, result) => {
if(err) reject(err);
else resolve(result);
})
})
}
const soapClient = {
delete: (value, cb) => {
setTimeout(() => cb(null, value), 10);
}
};
async function removeFromDNC(emailAddress, accessToken_in) {
const soapDelete = promisify(soapClient.delete.bind(soapClient));
const res = await soapDelete('Soap Responce');
//You can use res here
return res;
}
removeFromDNC().then(res => console.log(res))
I was wondering if it's possible to create a nameless function with the quick functional notation on javascript. What I mean by changing this:
var lobby = ((io) => {
...
}
return {
getWhiteBoardId: (name2, time2, role2, pass2, need_id) => {
let promise = new Promise((res, rej) => {
setTimeout(() => res("Now it's done!"), 1000)
});
// wait until the promise returns us a value
let result = await promise;
}
})(io);
I then want to later be able to call this function:
whiteboardId = lobby.getWhiteBoardId(req.body.name, req.body.time, req.body.role, req.body.pass, need_id);
to have something at the beginning such as this:
var lobby = (async (io) => {
so that I can call my Promise and await
// defining io, to execute the code, you not need to write the next line
const io = {};
const lobby = ((io) => {
// some pre-existing code
return {
// you are using await in this method so add async to the method signature
getWhiteBoardId: async (...args) => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(args), 1000)
});
// wait until the promise returns us a value
let result = await promise;
// ... some other code/logic as needed
// i have assumed you need to return the result
return result;
}
}
})(io);
// TEST: calling getWhiteBoardID method
lobby.getWhiteBoardId("Hi", "There").then(console.log);
// or
(async () => {
const res = await lobby.getWhiteBoardId("Hello World")
console.log(res);
})();
console.log("First!");
Let's say I have this observable:
const obs = new Observable((observer) => {
observer.next(0.25);
observer.next(0.75);
observer.next(new ArrayBuffer(100));
observer.complete();
});
How can I wait for each value with a promise?
The following code will only return the last value (value before complete() is called):
const value = await obs.toPromise();
But I want to be able to get each value along the way. I can do something like this:
const value1 = await obs.pipe(take(1)).toPromise();
const value2 = await obs.pipe(take(2)).toPromise();
But that's not ideal, since I'd have to increment the number each time and also take(1000) would still return something in the example, even though there are only 3 values. I'm looking for something like:
const value1 = await obs.pipe(next()).toPromise(); // 0.25
const value2 = await obs.pipe(next()).toPromise(); // 0.75
const value3 = await obs.pipe(next()).toPromise(); // ArrayBuffer(100)
const value4 = await obs.pipe(next()).toPromise(); // null
That is more akin to a generator.
Is there a way to accomplish something like this?
It seems like what you are asking for is a way to convert an observable into an async iterable so that you can asynchronously iterate over its values, either "by hand" or using the new for-await-of language feature.
Heres' an example of how to do that (I've not tested this code, so it might have some bugs):
// returns an asyncIterator that will iterate over the observable values
function asyncIterator(observable) {
const queue = []; // holds observed values not yet delivered
let complete = false;
let error = undefined;
const promiseCallbacks = [];
function sendNotification() {
// see if caller is waiting on a result
if (promiseCallbacks.length) {
// send them the next value if it exists
if (queue.length) {
const value = queue.shift();
promiseCallbacks.shift()[0]({ value, done: false });
}
// tell them the iteration is complete
else if (complete) {
while (promiseCallbacks.length) {
promiseCallbacks.shift()[0]({ done: true });
}
}
// send them an error
else if (error) {
while (promiseCallbacks.length) {
promiseCallbacks.shift()[1](error);
}
}
}
}
observable.subscribe(
value => {
queue.push(value);
sendNotification();
},
err => {
error = err;
sendNotification();
},
() => {
complete = true;
sendNotification();
});
// return the iterator
return {
next() {
return new Promise((resolve, reject) => {
promiseCallbacks.push([resolve, reject]);
sendNotification();
});
}
}
}
Use with for-wait-of language feature:
async someFunction() {
const obs = ...;
const asyncIterable = {
[Symbol.asyncIterator]: () => asyncIterator(obs)
};
for await (const value of asyncIterable) {
console.log(value);
}
}
Use without for-wait-of language feature:
async someFunction() {
const obs = ...;
const it = asyncIterator(obs);
while (true) {
const { value, done } = await it.next();
if (done) {
break;
}
console.log(value);
}
}
that might just work, since take(1) completes the observable, then it is consumed by await, next one in line will produce the second emission to value2.
const observer= new Subject()
async function getStream(){
const value1 = await observer.pipe(take(1)).toPromise() // 0.25
const value2 = await observer.pipe(take(1)).toPromise() // 0.75
return [value1,value2]
}
getStream().then(values=>{
console.log(values)
})
//const obs = new Observable((observer) => {
setTimeout(()=>{observer.next(0.25)},1000);
setTimeout(()=>observer.next(0.75),2000);
UPDATE: Using subject to emit.
I'm trying deal with a library that using async functions and am a little lost. I want to call a function that returns a string but am getting tripped up. Here's what I have so far. The ZeroEx library functions all seem to use async /await so my understanding is that I can only call them from another async method. But won't this just cause a chain reaction meaning every method needs to be async? Or am I missing something?
function main() {
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
broker.send([identity, '', getTopOfBook()]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
}
async function getTopOfBook() {
var result: string = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
throw new Error('could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
return result;
}
main();
The function getTopOfBook() isn't returning anything back to the main() function so the result is never being sent by the broker. The commented out broker.send() with 'TEST' is working fine however. Thanks for looking.
EDIT:
I tried to make the main method async so I could use await but it's giving an error that I can only use await in an async function. Could the broker.on() call be causing this?
const main = async () => {
try{
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
console.log(message);
if(message === 'TopOfBook') {
>> var test = await getTopOfBook();
console.log('in top of book test');
broker.send([identity, '', test]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
} catch (err) {
console.log(err);
}
}
EDIT 2:
My current working code, thanks everyone that had advice/solutions! I obviously have to fill out the getTopOfBook() function to return an actual result still. If you have more recommendations send them my way. I'm trying to build out a backend that will get data from a geth rpc and send it to a C# GUI front end.
var main = function() {
try{
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5672');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
getTopOfBook().then((result) => {
broker.send([identity, '', result])
});
}
})
} catch (err) {
console.log(err);
}
}
async function getTopOfBook() {
var result: string = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await zeroEx.exchange.getContractAddress();
const wethTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
throw new Error('could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
return result;
}
main();
The callback function needs to be async
broker.on('message', async function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
var test = await getTopOfBook();
broker.send([identity, '', test]);
}
//broker.send([identity, '', 'TEST']);
//console.log('test sent');
})
You are missing the point that async functions are just a way to write the code. Every async function what actually does is generate a promise. That is why you can await any promise and you can interact with any library that uses async without the need of using async functions yourself.
When you call an async function it should return a promise (which happens automatically on async function). A promise is nothing but an object with a then method. Such method accepts a callback, where you can handle the rest of the logic.
function main() {
var broker = zmq.socket('router');
broker.bindSync('tcp://*:5671');
broker.on('message', function () {
var args = Array.apply(null, arguments)
, identity = args[0]
, message = args[1].toString('utf8');
if(message === 'TopOfBook') {
return getTopOfBook().then( result =>
broker.send([identity, '', result])
) // If the broker also returns a promise, you can continue the flow here
.then(()=> console.log('test sent'))
}
})
}
Personally I don't like async await at all because the involve too much magic and make people to forget about the actual nature of promises and asynchronous code.
Also when dealing with promises you should always remember to return any promise you could call/generate so outer code can continue the chain and handle error messages.
Your function getTopOfBook returns a Promise, so you need to use the function then
Call that function as follow:
getTopOfBook().then((result) => {
console.log("Result:" + result);
});
Look at this code snippet
let sleep = (fn) => {
setTimeout(fn, 1000);
};
let getContractAddress = function(cb) {
return new Promise((r) => sleep(() => {
r('getContractAddress')
}));
};
let getTokenBySymbolIfExistsAsync = function(str) {
return new Promise((r) => sleep(() => {
r({
address: 'getTokenBySymbolIfExistsAsync: ' + str
})
}));
};
let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';
async function getTopOfBook() {
var result = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await getContractAddress();
const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
return Promise.reject(new Error('could not find token info'));
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
console.log(WETH_ADDRESS);
console.log(ZRX_ADDRESS);
console.log(EXCHANGE_ADDRESS);
return result;
}
var main = function() {
console.log('Waiting response...');
getTopOfBook().then((result) => {
console.log("Result:" + result);
console.log('DONE!');
}).catch((error) => {
console.log(error);
});
};
main();
.as-console-wrapper {
max-height: 100% !important
}
If you want to throw an error use the function Promise.reject()
Along with that call, you need either to pass the reject function or call the catch function.
In this example, we're passing the reject function:
(error) => {
console.log(error);
}
If you don't pass the reject function, you need to call the catch function in order to handle the thrown error.
let sleep = (fn) => {
setTimeout(fn, 1000);
};
let getContractAddress = function(cb) {
return new Promise((r) => sleep(() => {
r('getContractAddress')
}));
};
let getTokenBySymbolIfExistsAsync = function(str) {
return new Promise((r) => sleep(() => {
r()
}));
};
let WETH_ADDRESS = '';
let ZRX_ADDRESS = '';
let EXCHANGE_ADDRESS = '';
async function getTopOfBook() {
var result = 'test getTopOfBook';
const EXCHANGE_ADDRESS = await getContractAddress();
const wethTokenInfo = await getTokenBySymbolIfExistsAsync('WETH');
const zrxTokenInfo = await getTokenBySymbolIfExistsAsync('ZRX');
if (wethTokenInfo === undefined || zrxTokenInfo === undefined) {
return Promise.reject('Could not find token info');
}
const WETH_ADDRESS = wethTokenInfo.address;
const ZRX_ADDRESS = zrxTokenInfo.address;
console.log(WETH_ADDRESS);
console.log(ZRX_ADDRESS);
console.log(EXCHANGE_ADDRESS);
return result;
}
var main = function() {
console.log('Waiting response...');
getTopOfBook().then((result) => {
console.log("Result:" + result);
console.log('DONE!');
}, (error) => {
console.log(error);
}).catch((error) => {
console.log(error); // This line will be called if reject function is missing.
});
};
main();
.as-console-wrapper {
max-height: 100% !important
}
Resource
Promise.prototype.then()
Promise.reject()
Async function
When an async function is called, it returns a Promise. When the async function returns a value, the Promise will be resolved with the returned value. When the async function throws an exception or some value, the Promise will be rejected with the thrown value.
I have the promise function that execute async function in the loop few times for different data. I want to wait till all async functions will be executed and then resolve(), (or call callback function in non-promise function):
var readFiles = ()=>{
return new Promise((resolve,reject)=>{
var iterator = 0;
var contents = {};
for(let i in this.files){
iterator++;
let p = path.resolve(this.componentPath,this.files[i]);
fs.readFile(p,{encoding:'utf8'},(err,data)=>{
if(err){
reject(`Could not read ${this.files[i]} file.`);
} else {
contents[this.files[i]] = data;
iterator--;
if(!iterator) resolve(contents);
}
});
}
if(!iterator) resolve(contents); //in case of !this.files.length
});
};
I increase iterator on every loop repetition, then, in async function's callback decrease iterator and check if all async functions are done (iterator===0), if so - call resolve().
It works great, but seems not elegant and readable. Do you know any better way for this issue?
Following up the comment with some code and more detail!
Promise.all() takes an iterator, and waits for all promises to either resolve or reject. It will then return the results of all the promises. So instead of keeping track of when all promises resolve, we can create little promises and add them to an array. Then, use Promise.all() to wait for all of them to resolve.
const readFiles = () => {
const promises = [];
for(let i in files) {
const p = path.resolve(componentPath, files[i]);
promises.push(new Promise((resolve, reject) => {
fs.readFile(p, {encoding:'utf8'}, (err, data) => {
if(err) {
reject(`Could not read ${files[i]} file.`);
} else {
resolve(data);
}
});
}));
}
return Promise.all(promises);
};
const fileContents = readFiles().then(contents => {
console.log(contents)
})
.catch(err => console.error(err));
You only need push all the Promises into an array to then pass it as argument to Promise.all(arrayOfPromises)
try something like this:
var readFiles = () => {
var promises = [];
let contents = {};
var keys_files = Object.keys(this.files);
if (keys_files.length <= 0) {
var promise = new Promise((resolve, reject) => {
resolve(contents);
});
promises.push(promise);
}
keys_files.forEach((key) => {
var file = this.files[key];
var promise = new Promise((resolve, reject) => {
const currentPath = path.resolve(this.componentPath, file);
fs.readFile(p,{encoding:'utf8'},(err, data) => {
if (err) {
return reject(`Could not read ${file} file.`);
}
contents[file] = data;
resolve(contents)
});
});
});
return Promises.all(promises);
}
Then you should use the function like so:
// this will return a promise that contains an array of promises
var readAllFiles = readFiles();
// the then block only will execute if all promises were resolved if one of them were reject so all the process was rejected automatically
readAllFiles.then((promises) => {
promises.forEach((respond) => {
console.log(respond);
});
}).catch((error) => error);
If you don't care if one of the promises was rejected, maybe you should do the following
var readFiles = () => {
var promises = [];
let contents = {};
var keys_files = Object.keys(this.files);
if (keys_files.length <= 0) {
var promise = new Promise((resolve, reject) => {
resolve(contents);
});
promises.push(promise);
}
keys_files.forEach((key) => {
var file = this.files[key];
var promise = new Promise((resolve, reject) => {
const currentPath = path.resolve(this.componentPath, file);
fs.readFile(p,{encoding:'utf8'},(err, data) => {
// create an object with the information
let info = { completed: true };
if (err) {
info.completed = false;
info.error = err;
return resolve(info);
}
info.data = data;
contents[file] = info;
resolve(contents)
});
});
});
return Promises.all(promises);
}
Copied from comments:
Also - you might want to use fs-extra, a drop-in replacement for fs, but with promise support added.
Here's how that goes:
const fs = require('fs-extra');
var readFiles = ()=>{
let promises = files
.map(file => path.resolve(componentPath, file))
.map(path => fs.readFile(path));
return Promise.all(promises);
});
Nice and clean. You can then get contents like this:
readFiles()
.then(contents => { ... })
.catch(error => { ... });
This will fail on first error though (because that's what Promise.all does). If you want individual error handling, you can add another map line:
.map(promise => promise.catch(err => err));
Then you can filter the results:
let errors = contents.filter(content => content instanceof Error)
let successes = contents.filter(content => !(content instanceof Error))