I'm new to Node.js and was trying to get a lease from azure blob.
const leaseID = await acquireLease(container,blobName);
const result = await download(blob);
and acquireLease method is
const acquireLease = async function(container,blobName){
var leaseID;
blobService.acquireLease(container,blobName ,function(error,result,response){
if(!error) {
// Got lease
leaseID = result.id;
}
})
return(leaseID);
}
exports.acquireLease = acquireLease;
but before the acquireLease method gets completed and leaseID is got, the next main method
const result = await download(blob);
gets executed.
I tried promises but couldn't succeed.
const acquireLease = async function(container,blobName){
var leaseID;
return new Promise((resolve,reject)=>{
blobSvc.acquireLease(container,blobName ,function(error,result,response){
if(!error) {
// Got lease
leaseID = result.id;
}
})
resolve(leaseID);
})
}
exports.acquireLease = acquireLease;
Any suggestions or help appreciated.
You were really close with your Promise version! You just need to call resolve and reject at the right times.
Presumably, if error is truthy, then you should reject. Otherwise, it's safe to assume your result is valid and you can resolve the promise with result.id. There is no need for a leaseID variable. Also, if the function just returns a Promise, there's no need to mark it async.
const acquireLease = function (container, blobName) {
return new Promise((resolve, reject) => {
blobSvc.acquireLease(container, blobName, function (error, result, response) {
if (error) {
reject(error)
} else {
resolve(result.id)
}
})
})
}
exports.acquireLease = acquireLease;
Related
I am trying to return coordinates of given adress with gooogle maps geocoding API and fetch. I am able to log these coordinates inside my get fuction, but I have no idea how to return them from the function to use it somewhere else in the code. Already tried multiple varaitions of two approaches:
function getCoordinates1(name) {
locObj = fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykey`).then( (res) => res.json()).then( (data) =>
{
console.log(data.results[0].geometry.location);
return data.results[0].geometry.location;
}).then((res) => res);
}
let coordinates1 = getCoordinates1(latinaze(name2));
console.log(coordinates1);
async function getCoordinates2(name) {
locObj = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykeyk`).then( (res) => res.json()).then( (data) =>
{
console.log(data.results[0].geometry.location);
//return data.results[0].geometry.location;
}).then((res) => res);
return locObj
}
let coordinates2 = await getCoordinates2(latinaze(name2));
console.log(coordinates2);
First function returns undefined, second returns pending promise. What am I doing wrong?
The first function returns undefined because you don't return anything. As simple as that ;)
The second function returns a pending promise because you don't wait for the promise to be resolved. The promise gets resolved when the callback inside then is invoked, but that happens after you return locObj in getCoordinates2.
You should try this:
// function definition
async function getCoordinates3(name) {
const resp = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykeyk`)
const data = await resp.json();
return data.results[0].geometry.location;
}
// usage
const coordinates3 = await getCoordinates3(latinaze(name3));
I couldn't return any value from function, so I made it a class method and set a property in function body. Now I can get the value form that property after I call the function:
export default class SearchModel {
constructor() {
this.start = '';
this.meta = '';
this.coors = [];
this.address = 'none';
}
//translate coordinates to address
async getAdress(coordinates) {
try {
let geocodeCoordinates = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${this.coors[0]},${this.coors[1]}&key=${process.env.API_GM_KEY}`
const rawData = await fetch(geocodeCoordinates);
//console.log(await rawData.json());
return await rawData.json();
} catch (error) {
return new Error(`Wild ERROR occured, can't get LocObj. Details: ${error}`);
} }
async displayAdress(coordinates) {
const data = await this.getAdress(coordinates);
const dataAdress = await data.results[0].formatted_address;
this.address = await dataAdress; }
}
I would like to have a configuration file with variables set with data I fetch from an API.
I think I must use async and await features to do so, otherwise my variable would stay undefined.
But I don't know how to integrate this and keep the node exports.myVariable = myData available within an async function ?
Below is the code I tried to write to do so (all in the same file) :
const fetchAPI = function(jsonQuery) {
return new Promise(function (resolve, reject) {
var reqOptions = {
headers: apiHeaders,
json:jsonQuery,
}
request.post(apiURL, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
}
var wallsData = {}
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
let body = await utils.fetchAPI(jsonQuery)
let pageList = await body[0].dataHashes
for(i=0;i<pageList.length;i++){
var page = pageList[i]
wallsData[page.title.fr] = [page.difficultyList,page.wallType]
}
return wallsData
throw new Error("WOOPS")
}
try{
const wallsData = fetchWalls()
console.log(wallsData)
exports.wallsData = wallsData
}catch(err){
console.log(err)
}
The output of console.log(wallsData) shows Promise { <pending> }, therefore it is not resolved and the configuration file keep being executed without the data in wallsData...
What do I miss ?
Thanks,
Cheers
A promise is a special object that either succeeds with a result or fails with a rejection. The async-await-syntax is syntactic sugar to help to deal with promises.
If you define a function as aync it always will return a promise.
Even a function like that reads like
const foo = async() => {
return "hello";
}
returns a promise of a string, not only a string. And you need to wait until it's been resolved or rejected.
It's analogue to:
const foo = async() => {
return Promise.resolve("Hello");
}
or:
const foo = async() => {
return new Promise(resolve => resolve("Hello"));
}
Your fetchWalls similarly is a promise that will remain pending for a time. You'll have to make sure it either succeeds or fails by setting up the then or catch handlers in your outer scope:
fetchWalls()
.then(console.log)
.catch(console.error);
The outer scope is never async, so you cannot use await there. You can only use await inside other async functions.
I would also not use your try-catch for that outer scope promise handling. I think you are confusing the try-catch approach that is intended to be used within async functions, as there it helps to avoid nesting and reads like synchronous code:
E.g. you could do inside your fetchWalls defintion:
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
try {
let body = await utils.fetchAPI(jsonQuery)
} catch(e) {
// e is the reason of the promise rejection if you want to decide what to do based on it. If you would not catch it, the rejection would chain through to the first error handler.
}
...
}
Can you change the statements like,
try{
const wallsData = fetchWalls();
wallsData.then((result) => {
console.log(result);
});
exports.wallsData = wallsData; // when importing in other file this returns as promise and we should use async/await to handle this.
}catch(err){
console.log(err)
}
How can I add a setTimeout to my async await function call?
I have
request = await getProduct(productids[i]);
where
const getProduct = async productid => {
return requestPromise(url + productid);
};
I've tried
request = await setTimeout((getProduct(productids[i])), 5000);
and got the error TypeError: "callback" argument must be a function which makes sense. The request is inside of a loop which is making me hit the rate limit on an api call.
exports.getProducts = async (req, res) => {
let request;
for (let i = 0; i <= productids.length - 1; i++) {
request = await getProduct(productids[i]);
//I want to wait 5 seconds before making another call in this loop!
}
};
You can use a simple little function that returns a promise that resolves after a delay:
function delay(t, val) {
return new Promise(function(resolve) {
setTimeout(function() {
resolve(val);
}, t);
});
}
// or a more condensed version
const delay = (t, val) => new Promise(resolve => setTimeout(resolve, t, val));
And, then await that inside your loop:
exports.getProducts = async (req, res) => {
let request;
for (let id of productids) {
request = await getProduct(id);
await delay(5000);
}
};
Note: I also switched your for loop to use for/of which is not required, but is a bit cleaner than what you had.
Or, in modern versions of nodejs, you can use timersPromises.setTimeout() which is a built-in timer that returns a promise (as of nodejs v15):
const setTimeoutP = require('timers/promises').setTimeout;
exports.getProducts = async (req, res) => {
let request;
for (let id of productids) {
request = await getProduct(id);
await setTimeoutP(5000);
}
};
Actually, I have a pretty standard chunk of code that I use to do that:
function PromiseTimeout(delayms) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, delayms);
});
}
Usage:
await PromiseTimeout(1000);
If you're using Bluebird promises, then it's built in as Promise.timeout.
More to your problem: Have you checked API docs? Some APIs tell you how much you have to wait before next request. Or allow downloading data in larger bulk.
As of node v15 you can use the Timers Promises API:
const timersPromises = require('timers/promises');
async function test() {
await timersPromises.setTimeout(1000);
}
test();
Note that this feature is experimental and may change in future versions.
Since Node 15 and above, there is the new Timers Promises API that let you to avoid to build the wrapping:
import {
setTimeout,
setImmediate,
setInterval,
} from 'timers/promises';
console.log('before')
await setTimeout(1000)
console.log('after 1 sec')
So your issues you could write it with async iterator:
import {
setTimeout
} from 'timers/promises'
async function getProducts (req, res) {
const productids = [1, 2, 3]
for await (const product of processData(productids)) {
console.log(product)
}
}
async function * processData (productids) {
while (productids.length > 0) {
const id = productids.pop()
const product = { id }
yield product
await setTimeout(5000)
}
}
getProducts()
I have done api delay test as below.
It is possible to delay it as if by hanging setTimeout.
sleep(ms) {
const wakeUpTime = Date.now() + ms;
while (Date.now() < wakeUpTime) {}
}
callAPI = async() => {
... // Execute api logic
await this.sleep(2147483647);
... // Execute api logic
}
await callAPI();
I'm with Node.js and TypeScript and I'm using async/await.
This is my test case:
async function doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
I'd like to set a timeout for the overall function. I.e. if res1 takes 2 seconds, res2 takes 0.5 seconds, res3 takes 5 seconds I'd like to have a timeout that after 3 seconds let me throw an error.
With a normal setTimeout call is a problem because the scope is lost:
async function doSomethingInSeries() {
const timerId = setTimeout(function() {
throw new Error('timeout');
});
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
clearTimeout(timerId);
return 'simle';
}
And I cannot catch it with normal Promise.catch:
doSomethingInSeries().catch(function(err) {
// errors in res1, res2, res3 will be catched here
// but the setTimeout thing is not!!
});
Any ideas on how to resolve?
You can use Promise.race to make a timeout:
Promise.race([
doSomethingInSeries(),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 11.5e3))
]).catch(function(err) {
// errors in res1, res2, res3 and the timeout will be caught here
})
You cannot use setTimeout without wrapping it in a promise.
Ok I found this way:
async function _doSomethingInSeries() {
const res1 = await callApi();
const res2 = await persistInDB(res1);
const res3 = await doHeavyComputation(res1);
return 'simle';
}
async function doSomethingInSeries(): Promise<any> {
let timeoutId;
const delay = new Promise(function(resolve, reject){
timeoutId = setTimeout(function(){
reject(new Error('timeout'));
}, 1000);
});
// overall timeout
return Promise.race([delay, _doSomethingInSeries()])
.then( (res) => {
clearTimeout(timeoutId);
return res;
});
}
Anyone errors?
The things that smells a bit to me is that using Promises as asynchronicity strategy will send us to allocate too many object that some other strategy needs but this is off-topic.
Problem with #Bergi answer that doSomethingInSeries continues executing even if you already rejected the promise. It is much better to cancel it.
LATEST ANSWER
You can try use AbortController for that. Check the old answer to see how to use it - api is similar.
Keep in mind that task is not cancelled immediately, so continuation (awaiting, then or catch) is not called exactly after timeout.
To guarantee that you can combine this and #Bergi approach.
OLD ANSWER
This is how it should look like:
async const doSomethingInSeries = (cancellationToken) => {
cancellationToken.throwIfCancelled();
const res1 = await callApi();
cancellationToken.throwIfCancelled();
const res2 = await persistInDB(res1);
cancellationToken.throwIfCancelled();
const res3 = await doHeavyComputation(res1);
cancellationToken.throwIfCancelled();
return 'simle';
}
Here is simple implementation:
const makeCancellationToken = (tag) => {
let cancelled = false;
return {
isCancelled: () => cancelled,
cancel: () => {
cancelled = true;
},
throwIfCancelled: () => {
if (cancelled) {
const error = new Error(`${tag ?? 'Task'} cancelled`);
error.cancelled = true;
throw error;
}
}
}
}
And finally usage:
const cancellationToken = makeCancellationToken('doSomething')
setTimeout(cancellationToken.cancel, 5000);
try {
await doSomethingInSeries(cancellationToken);
} catch (error) {
if (error.cancelled) {
// handle cancellation
}
}
Keep in mind that task is not cancelled immediately, so continuation (awaiting, then or catch) is not called exactly after 5 secs.
To guarantee that you can combine this and #Bergi approach.
I'm running into an issue which I don't fully understand. I feel like there are likely concepts which I haven't grasped, code that could be optimized, and possibly a bug thrown in for good measure.
To greatly simplify the overall flow:
A request is made to an external API
The returned JSON object is parsed and scanned for link references
If any link references are found, additional requests are made to populate/replace link references with real JSON data
Once all link references have been replaced, the original request is returned and used to build content
Here, is the original request (#1):
await Store.get(Constants.Contentful.ENTRY, Contentful[page.file])
Store.get is represented by:
async get(type, id) {
return await this._get(type, id);
}
Which calls:
_get(type, id) {
return new Promise(async (resolve, reject) => {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if(isAsset(data)) {
resolve(data);
} else if(isEntry(data)) {
await this._scan(data);
resolve(data);
} else {
const error = 'Response is not entry/asset.';
console.log(error);
reject(error);
}
});
}
The API call is:
_api(type, id) {
return new Promise((resolve, reject) => {
Request('http://cdn.contentful.com/spaces/' + Constants.Contentful.SPACE + '/' + (!type || type === Constants.Contentful.ENTRY ? 'entries' : 'assets') + '/' + id + '?access_token=' + Constants.Contentful.PRODUCTION_TOKEN, (error, response, data) => {
if(error) {
console.log(error);
reject(error);
} else {
data = JSON.parse(data);
if(data.sys.type === Constants.Contentful.ERROR) {
console.log(data);
reject(data);
} else {
resolve(data);
}
}
});
});
}
When an entry is returned, it is scanned:
_scan(data) {
return new Promise((resolve, reject) => {
if(data && data.fields) {
const keys = Object.keys(data.fields);
keys.forEach(async (key, i) => {
var val = data.fields[key];
if(isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if(isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
if(i === keys.length - 1) {
resolve();
}
});
} else {
const error = 'Required data is unavailable.';
console.log(error);
reject(error);
}
});
}
If link references are found, additional requests are made and then the resulting JSON is injected into the original JSON in place of the reference:
_inject(fields, key, index, data) {
if(isNaN(index)) {
fields[key] = data;
} else {
fields[key][index] = data;
}
}
Notice, I'm using async, await, and Promise's I believe in their intended manor. What ends up happening: The calls for referenced data (gets resulting of _scan) end up occurring after the original request is returned. This ends up providing incomplete data to the content template.
Additional information concerning my build setup:
npm#2.14.2
node#4.0.0
webpack#1.12.2
babel#5.8.34
babel-loader#5.4.0
I believe the issue is in your forEach call in _scan. For reference, see this passage in Taming the asynchronous beast with ES7:
However, if you try to use an async function, then you will get a more subtle bug:
let docs = [{}, {}, {}];
// WARNING: this won't work
docs.forEach(async function (doc, i) {
await db.post(doc);
console.log(i);
});
console.log('main loop done');
This will compile, but the problem is that this will print out:
main loop done
0
1
2
What's happening is that the main function is exiting early, because the await is actually in the sub-function. Furthermore, this will execute each promise concurrently, which is not what we intended.
The lesson is: be careful when you have any function inside your async function. The await will only pause its parent function, so check that it's doing what you actually think it's doing.
So each iteration of the forEach call is running concurrently; they're not executing one at a time. As soon as the one that matches the criteria i === keys.length - 1 finishes, the promise is resolved and _scan returns, even though other async functions called via forEach are still executing.
You would need to either change the forEach to a map to return an array of promises, which you can then await* from _scan (if you want to execute them all concurrently and then call something when they're all done), or execute them one-at-a-time if you want them to execute in sequence.
As a side note, if I'm reading them right, some of your async functions can be simplified a bit; remember that, while awaiting an async function call returns a value, simply calling it returns another promise, and returning a value from an async function is the same as returning a promise that resolves to that value in a non-async function. So, for example, _get can be:
async _get(type, id) {
var data = _json[id] = _json[id] || await this._api(type, id);
console.log(data)
if (isAsset(data)) {
return data;
} else if (isEntry(data)) {
await this._scan(data);
return data;
} else {
const error = 'Response is not entry/asset.';
console.log(error);
throw error;
}
}
Similarly, _scan could be (assuming you want the forEach bodies to execute concurrently):
async _scan(data) {
if (data && data.fields) {
const keys = Object.keys(data.fields);
const promises = keys.map(async (key, i) => {
var val = data.fields[key];
if (isLink(val)) {
var child = await this._get(val.sys.linkType.toUpperCase(), val.sys.id);
this._inject(data.fields, key, undefined, child);
} else if (isLinkArray(val)) {
var children = await* val.map(async (link) => await this._get(link.sys.linkType.toUpperCase(), link.sys.id));
children.forEach((child, index) => {
this._inject(data.fields, key, index, child);
});
} else {
await new Promise((resolve) => setTimeout(resolve, 0));
}
});
await* promises;
} else {
const error = 'Required data is unavailable.';
console.log(error);
throw error;
}
}