I am trying to write a promise in such a way that I can pass some parameters. Inside, this promise will call a Fetch. This is the code I wrote so far:
const myFunction = (parA, parB, parC) => {
return new Promise ((resolve, reject) => {
url = ... // calculated based on the parameters passed above;
fetch(url)
.then(response => {
var object = response.json();
resolve(object);// the idea is that 'object' is returned by the outmost promise
console.log('Check');// So far OK, this is correctly printed to the console
})
.catch(err => {
console.log('Error: ' + err);
reject(err);
});
// resolve('1') // Used for test only. With this, everything works, but "object" here is undefined -- I guess it gets executed too early, before the completion of the Fetch
});
and this is where the promise is called
myFunction(a, b, c).then(res => {
console.log('OK');// just testing
console.log(res);// just testing
});
What happens is that the Fetch resolves OK, but my overall promise doesn't. The last two console.log instructions are never executed.
In short, my problem is: how can I resolve my promise returning the result from the Fetch? It's a promise inside a promise, I guess I could chain them with .then, but I also want to be able to pass parameters.
I also guess I could rewrite the code avoiding this promise chaining, but since I'm also doing it as a way of learning, I prefer to try to figure out first how to solve this problem with this structure.
p.s.: at the moment I am stuck with the promises, cannot use async/await because I am with an old version of node.js and cannot update it
As #VLAZ already mentioned, it's not necessary to create your own promise here, since fetch() itself already returns a promise.
Therefore, you could try this:
const myFunction = (parA, parB, parC) => {
const url = ... // calculated based on the parameters passed above;
return fetch(url)
.then(response => response.json())
.then(object => {
console.log('Check', object);
return object;
})
.catch(err => {
console.log('Error: ' + err);
});
};
Related
I have started to learn promises and have encountered a problem with sequencing data in my Vue app. Basically, I need to run a few functions in a specific order where each promise must be fully resolved and essential data must be set before the next function is called. I am currently trying to use Promise.all to achieve this but have had no success so far. The promises appear to resolve and fall through to the next function before the data is ready.
Here is an extract of the code.
const promisesArray = [this.loadPrivate(),this.loadPublic()]
await Promise.all(promisesArray).then(() => {
// Promises resolve and fall through to this function before data has been set
this.recordsCheck()
})
.catch((err) => { console.log('ERR IN PROMISE.ALL', err)})
Here is one of the promises. They are both more or less identical. As you can see it's in TypeScript but I don't think that is part of the problem.
async loadPublic(): Promise<TODO> {
new Promise(function(resolve, reject) {
// classes module is a promised-based API call which sets data to an array in a Vuex store
classesModule.getPublic().then((results) => {
// data not ready here yet
console.log('PUBLIC GROUPS IN DASH PROMISE ', results)
resolve() // Resolve triggers before the data is ready
}).catch(e => {
console.log('ERROR in dash.loadGroupClasses : ', e)
reject()
});
});
}
For brevity here is the gist of the classesModule.getPublic function.
#Action async getPublic(): Promise<TODO> {
await getPublicGroups("getPublicGroups", data).then(res => {
// do some work here
this.records.push(...records)
// Records have been correctly set here before the resolve
console.log('Public records array : ', this.records)
resolve()
})
The data definitely gets set eventually because I can see it in the console after the entire page has loaded. It just hasn't finished being set by the time the initial promises resolve and recordsCheck function is called, causing undefined errors.
Maybe there is an issue because of the multiple promises? I've kind of hit an impasse with it and would appreciate any help. Thanks in advance!
UPDATE
OK, based on some of the comments below I could see the problem. The functions in Promises.all were not actually returning promises. Yes, a bit of a silly oversight that I think I missed due to the multiple promises in the chain between different modules. So instead of this..
async loadPublic(): Promise<TODO> {
new Promise(function(resolve, reject) {
classesModule.getPublic().then((results) => {
console.log('PUBLIC GROUPS IN DASH PROMISE ', results)
resolve()
}).catch(e => {
console.log('ERROR in load : ', e)
reject()
});
});
}
I needed to do this...
async loadPublic(): Promise<TODO> {
return classesModule.getPublic().then((results) => {
console.log('PUBLIC GROUPS IN DASH PROMISE ', results)
}).catch(e => {
console.log('ERROR in load : ', e)
});
});
}
It appears to work correctly now. Thanks to all for tips. Really helped me out!
I am trying to use Jest to test a function that returns a promise.
The function manyUrls expects an array of URLs containing JSON data to be passed. The contents of these URLs are fetched and returned in an array.
I am testing the case where a valid URL string is passed. This throws a TypeError as expected, but I'm having trouble using promises to write a test for that (async/await works fine)
//index.js
const manyUrls = (urls) => {
return Promise.all(urls.map(url => fetch(url)))
.then(responses =>
Promise.all(responses.map(res => res.json()))
).then(texts => {
return Promise.resolve(texts)
}).catch(error => {
return Promise.reject(error)
})
}
//index.test.js
const manyUrls = require("./index")
const url = 'https://jsonplaceholder.typicode.com/todos/1'
test('Expect TypeError if a single string is passed', () => {
// expect.assertions(1);
// try {
// await manyUrls(url);
// } catch (e) {
// expect(e).toBeInstanceOf(TypeError);
// }
return manyUrls(url).catch(e =>
expect(e).toBeInstanceOf(TypeError)
)
})
I have left commented out the bit of code that successfully passes that test case using async/await (if I add async to the callback) but I am trying to make this work with promises. I have followed the documentation on https://jestjs.io/docs/asynchronous but can't seem to get that test to pass. Does anyone know why?
Thanks in advance,
Your return manyUrls(url)... code would work just fine if your manyUrls function returned a Promise - but it doesn't when the urls parameter is not an Array.
There is a big difference between a function that returns a Promise which rejects with an Error and a function which throws an Error. By using return in your test, you are telling Jest that your function is the former, when in actuality your function is the latter.
In manyUrls, the first line, return Promise.all(urls.map(url => fetch(url))) tries to call urls.maps but throws an Error when urls is not an Array (as in the test case). In throwing the Error, no Promise is returned.
Your try/catch implementation works because it catches the Error that manyUrls is throwing. If you want the return implementation to work, then manyUrls must return a Promise that rejects.
The simplest way to update manyUrls to always return a Promise would be to add the async keyword - as in const manyUrls = async (urls) => {. By making it an async function, any Errors thrown from manyUrls will automatically be turned into the rejection value of the returned Promise. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function#return_value
What is the difference between these two? Is one faster than the other? Both seem to work. Someone please explain
One with no promise:
client.query(query1)
.then(data => {
callback(null, {
statusCode: 200,
body: JSON.stringify(data)
});
.catch(err => {
callback(null, {
statusCode: 500,
body: JSON.stringify(err)
});
});
Other with a promise:
return new Promise((resolve, reject) => {
client.query(query2)
.then(data => {
resolve({statusCode:200, body: JSON.stringify(data)});
})
.catch(err => {
reject(err);
});
});
Lets start with what these 2 code snippets have in common. They both invoke some client.query() function which we know nothing about, however we can guess it returns a Promise instance. I will assume this for the rest of the answer.
What is the difference between these two?
The difference between the two is how they return the result from an asynchronous operation. While the first one uses a callback, the latter uses a Promise, which is more than just a callback. It provides state (pending / fulfilled / rejected) and supports chaining. You could, of course, chain callbacks too, but it will inevitably end up in callback hell. In the second snippet you could have returned the original promise, but in my opinion wrapping it in your own promise is probably a good thing, because you're abstracting away the results returned the original promise.
Is one faster than the other?
I wouldn't worry about this matter. In asynchronous programming it's usually waiting for the result of aynchronous operation that takes the most time. The mechanism how you report the result of such operation to caller is irrelevant from performance point of view. What however matters in asynchronous programming is code readability and maintainability, which might not be optimal even if you use promises. And that leads us to async functions introduced in ES2017.
I'm not encouraging you to use async function straight away. Just be aware of it, study it and use when appropriate.
the short answer is then always returns a Promise.
when any promise is rejected within the chain it will execute the first catch. (throwing an error within then will return a rejected promise).
then will return a promise of the returned value if it's not a promise and return a rejected promise if an error was thrown.
so the first one is also returning a Promise of undefined, and there's no difference in performance,
const p1 = Promise.resolve('this will resolve');
p1.then((x) => Promise.resolve('You know: ' + x)).then(str => console.log(str));
p1.then((x) => Promise.reject('this will not resolve')).then(x => console.log('this will never execute')).catch(e => console.log('told you : ' + e));
p1.then((x) => 'You know: ' + x).then(str => console.log(str));
p1.then((x) => { throw 'this will not resolve' }).then(x => console.log('this will never execute')).catch(e => console.log('told you : ' + e));
Both of these methods do the same thing within then.
This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
I am writing a Node.js script to populate an SQL database with a test dataset.
In the promise chain illustrated in the code snippet below (the real code is a bit more hairy), the function insertData() requires the db object to be passed through from the previous stage. However, asynchronous calls inside dropAndCreateTables() on the previous stage use db object but do not return it. I came up with a solution that wraps promises inside dropAndCreateTables() into another promise object that resolves to a db object. However:
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
I heard that nesting then()-chains is also an antipattern
It does not allow me to ignore errors from the promiseDrop (for example, I don't care if tables don't exist on drop)
It is ugly
Questions:
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Code:
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
const promiseDrop = db.raw(sqlDrop)
const sqlCreate = fs.readFileSync('create.sql').toString()
const promiseCreate = db.raw(sqlCreate)
/********* Problems here? ************************************/
return new Promise((resolve, reject) => { // Ew?
Promise.all([promiseDrop, promiseCreate])
.then(() => {
resolve(db) // Override the returned value
})
.catch(reject)
})
}
return Promise.resolve(db)
}
initDB({ debug: false })
.then((db) => {
return dropAndCreateTables(db, START_CLEAN) // Without my hack this does not return `db`
})
.then((db) => {
return insertData(db, DO_UPSERT) // This needs the `db` object
})
.then(() => {
console.info(`\n${timestamp()} done`)
})
.catch(handleError)
(Some fairly important notes midway and later in the answer, please do read all the way to the end.)
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Yes, you simply return a value from the then handler, and return the promise then returns:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db);
then (and catch) create promise chains. Each link in the chain can transform the result. then and catch return a new promise that will be fulfilled or rejected based on what happens in their callback:
If their callback throws, the promise rejects with the error thrown
If their callback returns a non-thenable value (e.g., promise), the promise is fulfilled with that value
If their callback returns a thenable value, the promise is resolved to that thenable — it waits for the other promise to settle, then settles the same way
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve," I go into promise terminology in this post on my blog.)
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
The distinction isn't library code vs. non-library code, it's between code that doesn't already have a promise to work with and code that does. If you already have a promise to work with, you almost never want to use new Promise. More: What is the explicit promise construction antipattern and how do I avoid it?
I heard that nesting then()-chains is also an antipattern
You almost never need to nest then chains, because again each link in the chain already has the means of tranforming the result passing through it. So:
// Unnecessary nesting
doSomething()
.then(a => {
return doSomethingElse(a * 2)
.then(b => b * 3);
})
.catch(e => { /*...handle error...*/ });
can be more idiomatically and simply written:
doSomething()
.then(a => doSomethingElse(a * 2))
.then(b => b * 3);
.catch(e => { /*...handle error...*/ });
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Not an X/Y per se, but you have a problem in that code: There's no guarantee the drop will happen before the create! So instead of starting both and letting them run in parallel and watching for the results with Promise.all, ensure those operations happen in sequence:
// Fairly minimal changes
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
return db.raw(sqlDrop)
.then(() => {
const sqlCreate = fs.readFileSync('create.sql').toString()
return db.raw(sqlCreate);
})
.then(() => db);
}
return Promise.resolve(db)
}
But, I wouldn't use sync file I/O. Instead
const promisify = require("utils").promisify;
const readWithPromise = promisify(fs.readFile);
and then
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const getDrop = readWithPromise('drop.sql'); // Start this first
const getCreate = readWithPromise('create.sql'); // Then start this
return getDrop
.then(dropSql => db.raw(dropSql)) // Got the drop SQL, run it
.then(() => getCreate) // Make sure we have the create SQl
.then(createSql => db.raw(createSql)) // Run it
.then(() => db);
}
return Promise.resolve(db)
}
Note how we avoid ever busy-waiting on I/O, and we can overlap the DB's drop operation with reading the create SQL.
You don't need to call the Promise constructor when returning another promise, you can just write it like:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db)
.catch(error => {
// handle the error or rethrow it
})
You might omit resolving db from dropAndCreateTables like this:
.then((db) => {
return dropAndCreateTables(db, START_CLEAN).then(Promise.resolve(db));
})
You should not let dropAndCreateTables return a db promise, there is no real usecase for it. So:
return Promise.all([promiseDrop, promiseCreate]);
is enough. Now the chaining part:
initDB({ debug: false }).then(async (db) => {
await dropAndCreateTables(db, START_CLEAN);
await insertData(db, DO_UPSERT);
console.info(`\n${timestamp()} done`)
}).catch(handleError)
I am using a promise to get some JSON from a URL. The JSON that is returned includes a list of new URLs that return JSON. My current implementation is failing due to the nested promises.
I need to do the following:
request parent JSON url
request each of the child JSON urls
After each child promise returns JSON, I need to do some stuff with the child's JSON and the parent JSON.
I am getting the following error.
Warning: a promise was created in a handler at main.development.js:661:61 but was not returned from it
Boiled down version of my code:
myPromise(url)
.then(response => {
// process the data into an array of items
items.forEach(item => {
myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
});
});
Here I've done your example, using Bluebird map.
I've also added the concurrency option, this is very handy.. Leaving out, will just work a bit like promise.all, and putting a value of 1, would be were you want to do all the promises in series..
myPromise(url)
.then(response => {
// process the data into an array of items
return Promise.map(items, item => {
return myPromise(item.url)
.then(response2 => {
// Do a thing here with data from response and response2
});
}, {concurrency:10}); //lets do a max of 10 promises at a time.
});
You error is actually just a warning. It is there for good reason; a common mistake is doing something like this
myPromise(url)
.then(response => {
somethingElseAsync(response);
})
.then(myCallback);
and expecting myCallback to be invoked after somethingElseAsync has finished work. As far as I can tell, this is not your case, since you are not collecting the results of your child promises.
To suppress the warning, you can follow Keith's answer. As a bonus, you can tack another promise onto your chain which will resolve when all child promises have resolved.
As an alternative to Promise.map, if you are okay with spawning all child tasks simultaneously, you can get away with Promise.all, like this:
myPromise(url).then(response => {
return Promise.all(items.map(item => {
return myPromise(item.url).then(response2 => {
// handle response and response2, return some result
return result;
});
}));
}).then(results => {
// ^^^ an array of results returned from child promise callbacks
}).catch(error => {
// either the parent promise or one of the child promises has rejected
});