Say I have an array of objects which have asynchronous methods:
[
{
partOne: function(input) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
},
partTwo: function(result) {
// Do something w/ result of partOne
}
}
]
I want to execute partOne of the first object with my input, pass the result (async) to the partTwo callback, then pass the result of partTwo as input to partOne of the next object and so on. The array may be of one or more objects. I'm wondering what the best pattern to execute this kind of code is?
It is somewhat similar to the waterfall method of async.js: https://caolan.github.io/async/docs.html#waterfall, but I wonder how I can do this without a library and possibly with cleaner code?
Not sure if async/await might help here?
Another option without collecting every callback to an array, using async/await:
async function processWaterfallObject (data, input) {
let result = input
for (let entry of data) {
result = await entry.partOne(result)
result = await entry.partTwo(result)
}
return result
}
This assumes that functions in your data array are either async or return a Promise.
async/await is currently supported by every major browser and is available in node since 7.6.0.
Here is a simple function to invoke each asynchronous function in a stack
function drain(stack, initialArg) {
stack.reduce(function (sectArg, section) {
return Object.keys(section).reduce(async function (arg, key) {
return await section[key].call(null,arg)
}, sectArg)
}, initialArg)
}
To use it ensure that each function in you stack returns a value
var stack = [
{
partOne: function(input) {
// Do something async
console.log('section one partOne', input)
return 'section one partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section one partTwo', result)
return 'section one partTwo output'
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
console.log('section two partOne', resultOfPrevious)
return 'section two partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section two partTwo', result)
return 'section two partTwo output'
}
},
{
partOne: function(resultOfPrevious) {
// Do something async
console.log('section three partOne', resultOfPrevious)
return 'section three partOne output'
},
partTwo: function(result) {
// Do something w/ result of partOne
console.log('section three partTwo', result)
return 'section three partTwo output'
}
}
]
So that you can invoke the stack like
drain(stack, 'initialArg')
See this jsfiddle: https://jsfiddle.net/kqj0rror/
Assuming your array of objects given in the original question is under a variable called waterfall
let collector = [];
for (waterfallObj of waterfall) {
let tempArr = Object.values(waterfallObj);//get the functions out of the object
for (waterfallFunc of tempArr) {
collector.push(waterfallFunc);
}
}
//now you have your functions in order in collector
function recursiveCallback(i) {
if (i>collector.length-1) {
return;//if there are no more to call then return
}
collector[i]().then(function(){
recursiveCallback(i+1);
});
}
If you want the next function to do something with the previous functions value then simply change the then to then(function(passedValue and then use that passedValue in the recursiveCallback call within it
Related
I am trying to verify if the user is inside that list that I capture by axios, the issue is that I have used the FILTER option but it always returns undefined or [], being that if the user exists in that array.
I can't think what else to do, because I validate if it is by console.log() the variable with which I ask and if it brings data.
created() {
this.getStagesDefault()
this.getSalesman()
this.getStagesAmountByUser()
},
methods: {
async getSalesman(){
const { data } = await axios.get('salesman')
this.employees = data.data
},
getStagesAmountByUser(){
console.log(this.user['id'])
var objectUser = this.employees.filter(elem => {
return elem.id === this.user['id']
})
console.log(objectUser)
},
Console
Vue data
The method getSalesman is asynchronous, meaning that getStagesAmountByUser will start executing before getSalesman finishes.
Two ways to fix the problem:
Await the getSalesman method, but you have to make the created method async as well. Change the code as follows:
async created() {
this.getStagesDefault()
await this.getSalesman()
this.getStagesAmountByUser()
}
Attach a .then to the getSalesman function, and start the next one inside the .then. Change the code as follows:
created() {
this.getStagesDefault()
this.getSalesman().then(() => this.getStagesAmountByUser())
}
getSalesman is an async method. At the time of the filter, the array being filtered is still empty.
this.getSalesman() // this runs later
this.getStagesAmountByUser() // this runs right away
Have the methods run sequentially by awaiting the async method:
await this.getSalesman()
this.getStagesAmountByUser()
You can avoid the inefficient clientside filtering if you pass the id to the backend and only select by that id.
Additionally, created only gets called once unless you destroy the component which is also inefficient, so watch when user.id changes then call your method again.
Plus don't forget you must wrap any async code in a try/catch else you will get uncaught errors when a user/salesman is not found etc, you can replace console.error then with something which tells the user the error.
{
data: () => ({
employee: {}
}),
watch: {
'user.id' (v) {
if (v) this.getEmployee()
}
},
created() {
this.getEmployee()
},
methods: {
getEmployee() {
if (typeof this.user.id === 'undefined') return
try {
const {
data
} = await axios.get(`salesman/${this.user.id}`)
this.employee = data.data
} catch (e) {
console.error(e)
}
}
}
}
Basically, i'm so confused with the Promises. Because, I'm still new to moved on from Callback method for handling Asynchronous.
So, i have a code like these
const Search = function(name) { //
return new Promise((resolve, reject) => { //
let o = [{
"name": "Apple",
"quantity": 10
},
{
"name": "Grape",
"quantity": 5
}
]
let result = o.filter((n) => n.name == name)
if (result.length < 1) {
reject()
return
}
resolve(result[0])
})
}
const Buy = function(data, qty) {
return new Promise((resolve, reject) => {
let o = {
name,
quantity
} = data
o.quantity = parseInt(o.quantity) - qty
if (o.quantity < 0) {
throw new Error("Oops. Quantity is Empty!")
}
resolve(o)
})
}
const Result = function(test) {
console.log(test)
}
The main purpose, how i can input a value into a qty arguments on Buy function?
I was do something like this, but the result is not expected what i want. I'm sure, my code has something missing but i don't know.
Search("Apple")
.then(Buy, 4)
.then(Result)
The result is :
{ name: 'Apple', quantity: NaN }
Main goal is :
{ name: 'Apple', quantity: 6 }
Anyway thanks :)
Search("Apple")
.then(function(result){return Buy(result, 4)})
.then(Result)
You were trying to pass Buy directly into .then, but the function in .then always gets passed only 1 argument. So you can call and return Buy in an anonymous function, where you can apply yourself as many arguments you want.
You can take advantage of scope.
function executeSearch() {
Search("Apple").then(function (searchResult) {
// feel free to use the result of the first promise
// as input parameters to the second if desired
const name = searchResult.name;
const quantity = searchResult.quantity;
Buy(searchResult, 4).then(Result);
});
}
This is similar to the other answer but demonstrates that you can use the result of the first promise in any number of ways to execute the second promise.
The then method accepts a function, so what you can do is change your 'buy' to the following:
const Buy = function(qty) {
return function(data){
return new Promise((resolve, reject) => {
let o = {
name,
quantity
} = data
o.quantity = parseInt(o.quantity) - qty
if (o.quantity < 0) {
throw new Error("Oops. Quantity is Empty!")
}
resolve(o)
})
}
}
Then you can use it like:
Search("Apple")
.then(Buy(4))
.then(Result)
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
I'm trying to chain promises together to make a series of requests. This is one of the few times I've used promises, so I'm not too sure what I'm doing. Hoping that someone can catch whats going on wrong. Basically, my first promise is working correctly, then the second function runs after the first promise is resolved, however, the third does not wait so the information I'm requesting is incomplete.
I can see that my functions are returning the correct information. So mainly my question is how do I format my promises so they each wait for each other to complete before running?
function getPaths(folderpath) {
return new Promise(function(resolve, reject) {
db.getPaths(folderpath, function(response) {
//get paths for files and folders
for (let i = 0; i < response.entries.length; i++) {
let entries = response.entries[i];
if (entries[".tag"] == "folder") {
folderPaths.push(response.entries[i].path_lower);
}
if (entries[".tag"] == "file") {
filePaths.push(response.entries[i].path_lower);
}
}
resolve();
});
});
}
function openFolders(folders) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < folders.length; i++) {
db.getPaths(folders[i], function(response) {
for (let j = 0; j < response.entries.length; j++) {
let entries = response.entries[j];
if (entries[".tag"] == "file") {
filePaths.push(response.entries[j].path_lower);
}
}
//console.log(filePaths); //returns correct information
});
}
resolve();
});
}
getPaths("/path").then(function() {
openFolders(folderPaths);
}).then(function() {
console.log(filePaths); //returns incomplete information
});
You have a big problem here in bold
function openFolders(folders) {
return new Promise(function(resolve, reject) {
for (let i = 0; i < folders.length; i++) {
db.getPaths(folders[i], function(response) {
// ...
});
}
resolve();
});
}
You're running a synchrounous for loop around an asynchronous function call. The loop finishes iterating synchronously and then immediately calls resolve() - the code in the async handler (eg, filePaths.push(...)) does not run before the Promise is resolved
Your problems don't stop there though. You're using Promises unconventionally by modifying global state and then resolving and empty Promise - resolve() vs resolve(someValue). #FrankModica's answer has more to say about that.
As for remedies, I'd recommend you take a look at Promise.all - and in general, using Promises in a more conventional way
In the snippet below, I've mocked your db function to read from a fake in-memory database, fakedb. Then, I made a getPaths function which wraps db.getPaths but returns a Promise instead. Then I rewrite openFolders to create an array of Promises, pass them to Promise.all, then finally resolve the values I want
Worth noting: Promise.all will run your array of Promises in parallel - if this undesirable, you could run them in serial using .reduce and .then.
const db = {
getPaths: (path, k) => {
setTimeout(k, 50, fakedb[path])
}
}
function getPaths (path) {
return new Promise((resolve, reject) =>
db.getPaths(path, response => resolve(response)))
}
function openFolders (folders) {
return Promise.all(folders.map(getPaths))
.then(responses =>
responses.reduce((acc, {entries}) =>
acc.concat(entries.filter(x => x['.tag'] === 'file').map(x => x.path_lower)), []))
}
const fakedb = {
'cat': {
'entries': [
{ '.tag': 'dir', 'path_lower': './cat/.' },
{ '.tag': 'dir', 'path_lower': './cat/..' },
{ '.tag': 'file', 'path_lower': './cat/a' },
{ '.tag': 'file', 'path_lower': './cat/b' },
{ '.tag': 'file', 'path_lower': './cat/c' }
]
},
'dog': {
'entries': [
{ '.tag': 'dir', 'path_lower': './dog/.' },
{ '.tag': 'dir', 'path_lower': './dog/..' },
{ '.tag': 'file', 'path_lower': './dog/a' },
{ '.tag': 'file', 'path_lower': './dog/b' },
{ '.tag': 'file', 'path_lower': './dog/c' }
]
}
}
openFolders(['cat','dog']).then(console.log, console.error)
The operation I'm performing in openFolders might feel a little complex because it handles all transformations of the responses in a single .then handler - you could (optionally) separate the work into multiple .then calls which might result in a lighter cognitive load
function openFolders (folders) {
return Promise.all(folders.map(getPaths))
// get `.entries` of each response
.then(responses =>
responses.map(x => x.entries))
// flatten array of entries arrays into a single array
.then(arrOfEntries =>
arrOfEntries.reduce((acc, entries) =>
acc.concat(entries), []))
// only keep entries where `.tag` of each entry is `'file'`
.then(entries =>
entries.filter(x => x['.tag'] === 'file'))
// return `path_lower` of the resulting entries
.then(entries =>
entries.map(x => x.path_lower))
}
If I understand correctly, you are using some variables from the outer scope (folderPaths and filePaths). I'm not sure that's the best way to go about this, but I believe it will start working once you return the promise from openFolders. This way you wait until openFolders completes:
getPaths("/path").then(function() {
return openFolders(folderPaths);
}).then(function() {
console.log(filePaths);
});
But I'd recommend resolving the information required for the next call:
resolve(folderPaths);
And:
resolve(filePaths);
So your code can look more like:
getPaths("/path").then(function(folderPaths) {
return openFolders(folderPaths);
}).then(function(filePaths) {
console.log(filePaths);
});
Edit: Looks like you may have another issue mentioned by #naomik. If db.getPaths is async, in your openFolders function you may be resolving before all of the async calls in the loop complete. Unfortunately I don't have time right now to show the solution. Hopefully #naomik can!
I used to have the following code:
function makeCall(userInfo) {
api.postUser(userInfo).then(response => {
utils.redirect(response.url);
})
// other logic
return somethingElse;
}
And I was able to write a test that looked like this:
const successPromise = Promise.resolve({ url: 'successUrl' })
beforeEach(function() {
sinon.stub(api.postUser).returns(successPromise);
}
afterEach(function() {
api.postUser.restore();
}
it "calls API properly and redirects" do
makeCall({});
expect(api.postUser).calledWith(userInfo).toBe(true);
successPromise.then(() => {
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
}
emd
And everything was green.
Now, I had to add another promise to make another external call, before doing the api postUser call, so my code looks like this:
function makeCall(names) {
fetchUserData(names).then(userData => {
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
// other logic
return somethingElse;
}
where fetchUseData is a chain of many promises, such like:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
And the tests I had fail. I am trying to understand how to change my tests to make sure that I am still testing that I do the final API call properly and the redirect is also done. I want to stub what fetchUserData(names), to prevent doing that HTTP call.
You're not using promises correctly. Your code doesn't have a single return statement, when it should have several (or it should be using arrow functions in such a way that you don't need them, which you're not doing).
Fix your code:
function makeCall(names) {
// v---- return
return fetchUserData(names).then(userData => {
// v---- return
return api.postUser(userData).then(response => {
utils.redirect(response.url);
})
})
}
function fetchUserData(names) {
// v---- return
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
// v---- return
return {
id: users[0].id,
name: users[0].name,
}
});
}
Once you've done that, you can have your test wait for all of the operations to finish.
Test code:
makeCall(['name']).then(() =>
expect(api.postUser).calledWith(userInfo).toBe(true);
expect(utils.redirect.calledWith('successUrl')).toBe(true);
done();
});
You should add a return statement, otherwise you are not returning promises nowhere:
function fetchNames(names) {
// some name regions
return Promise.all(names);
}
function fetchUserData(names) {
return fetchUsersByNames(names).then(users => {
// For now we just choose first user
{
id: users[0].id,
name: users[0].name,
}
});
}
So when you are using Promise.all(), then you will have as result of the promise an array with all the value returned by all the promises.
So then this method should look like this when called:
fetchNames(names).then((arrayOfResolvedPromises) => {
// here you will have all your promised resolved and the array holds all the results
});
So inside your test you can move your done inside the block where all the promises will be resolved.
In addition, I strongly suggest you to use a library as chai-as-promised for testing promises.
It has a lot of nice methods for testing your promises.
https://github.com/domenic/chai-as-promised
This is how I'm doing it:
const Document = Parse.Object.extend('Document')
const query = new Parse.Query(Document)
let result = {}
query.get(id, {
success: function (object) {
result = object.toJSON()
console.log(result)
},
error: function (object, error) {
alert(`Error: ${error.code} ${error.message}`)
}
})
console.log(result)
return result
The first console.log(result) outputs the object:
Object {content: "trstrtrts", createdAt: "2016-01-17T11:20:30.694Z",
title: "Document 2", updatedAt: "2016-01-17T11:20:30.694Z", wordCount:
"3000"…}
But the second one returns nothing. What's the correct way of returning an object from a Parse query?
EDIT:
Based on Anon's answer I tried this:
store.js:
store.first = (id) => {
var query = new Parse.Query(Document)
return query.get(id)
}
export default store
main.js:
store.first(to.params.id).then((document) => {
console.log(document.toJSON())
return document.toJSON()
})
But I get the following error:
Uncaught TypeError: Object function ParsePromise() {
_classCallCheck(this, ParsePromise); this._resolved = false; this._rejected = false; this._resolvedCallbacks = [];
this._rejectedCallbacks = []; } has no method 'all'
The second
console.log(result)
take place before the first one.Query is an async operation.
The correct way of doing this is to use promises. For example you can do
function foo(id){
var Document = Parse.Object.extend('Document');
var query = new Parse.Query(Document);
return query.get(id);
}
and then use the function foo like this:
foo(objectID).then(function(object){
//do something with the object.
})
here is an example to show the async in js.
console.log('a');
setTimeOut(function(){console.log('b')},0);
console.log('c');
the order of the printing is
a
c
b
(we have time out 0 but the function of the timeout goes in the event loop and take place after the function done)
for more information you can read https://developer.mozilla.org/he/docs/Web/JavaScript/EventLoop
about the eventloop