What is this function doing in JavaScript? - javascript

I'm learning JavaScript and I'm learning about Promises. I'm having a hard time to understand the following code:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true);
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" });
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(
isAuth => {
if (isAuth) {
return fetchUser()
}
}
)
.then(
user => {
setText(user.name)
}
)
});
I understand the first two functions checkAuth and fetchUser, they return a resolved Promise after 2 seconds.
I can't really understand how does the event listener of button work. I see it calls checkAuth() and when executed, the div text of the HTML document changes to Checking Auth... for two seconds (until the function resolves successfully the Promise. When resolved, it enters into the .then() part of the code.
I'm struggling to understand where does isAuth and user come from. I feel like they are some implicit declaration of a function (called inside themselves?), it's looking really odd to me.

checkAuth() returns a promise and the value with which it resolves is passed to the callback function of checkAuth().then(...) which is then assigned to isAuth parameter.
In the case of user, it is the value with which the promise returned by checkAuth().then() is resolved. If the callback function of checkAuth().then(...) returns a non-promise value, user will be that non-promise value.
If the callback function of checkAuth().then() returns a promise, then user will be the value with which that promise, returned by the callback function, resolves.
Here's a simplified example that might help you understand how isAuth and user are assigned a value:
Promise.resolve(true)
.then(isAuth => {
console.log("isAuth: " + isAuth);
return { name: "John Doe" };
})
.then(user => console.log(user));

Here is the explanation:
const button = document.querySelector("button");
const div = document.querySelector("div");
const setText = (text) => {
div.textContent = text
}
const checkAuth = () => {
return new Promise((resolve, reject) => {
setText('Checking Auth...')
setTimeout(() => {
resolve(true); // The return value when the promise resolves which is in this case is `true`;
}, 2000);
});
};
const fetchUser = () => {
return new Promise((resolve, reject) => {
setText('Fetching User...');
setTimeout(() => {
resolve({ name: "Max" }); // Here when the promise resolves it is returning `{ name: "Max" }`;
}, 2000);
});
};
button.addEventListener("click", () => {
checkAuth()
.then(isAuth => { // This is an arrow function, you can see we are accepting the `isAuth` parameter;
if (isAuth) { // Regular if...
return fetchUser() // And the this also returns an promise
}
}
)
.then(user => { // Which is resolved here, And the user is the `{ name: "Max" }`;
setText(user.name)
}
)
});

isAuth is returned by checkAuth and user is returned by fetchUser.
I'll re-write this in async/await format. You can compare to make better sense of it.
const delay = (time) => new Promise(res => setTimeout(res(), time));
const checkAuth = async () => {
setText('Checking Auth...');
await delay(2000);
}
const fetchUser = async () => {
setText('Fetching User...');
await delay(2000);
}
const executeOrder = async () => {
const isAuth = await checkAuth();
if (isAuth) {
const user = await fetchUser();
setText(user.name)
}
}
button.addEventListener("click", executeOrder);

Related

Proper use of async JS to ensure function calls wait for previous functions to complete (resolve?)

Trying to learn proper async/await JavaScript to run functions in sequence when an early function in the sequence would be delayed (using setTimeout to simulate). I'm not getting the expected results (still getting "first", "second", "this should run first?" - see code).
What am I missing? Do I have the wrong idea about this?
Thanks in advance!
const zeroFunction = () => {
setTimeout(() => {
return new Promise((resolve) => {
console.log("This should run first?");
resolve();
});
}, 2000)}
const firstFunction = () => {
return new Promise((resolve) => {
console.log("first");
resolve();
})
}
const secondFunction = () => {
return new Promise((resolve) => {
console.log("second");
resolve();
})
}
async function fnAsync() {
await zeroFunction();
await firstFunction();
secondFunction();
}
fnAsync();
zeroFunction is currently returning undefined implicitly, not a Promise. Inverse the wrapping of the setTimeout and Promise constructor and it should work as expected.
const zeroFunction = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log("This should run first?")
resolve()
}, 2000)
})
}
const firstFunction = () => {
return new Promise((resolve) => {
console.log("first")
resolve()
})
}
const secondFunction = () => {
return new Promise((resolve) => {
console.log("second")
resolve()
})
}
async function fnAsync() {
await zeroFunction()
await firstFunction()
secondFunction()
}
fnAsync()

Chaining three different async functions

I have three async functions but the third function is running before the second one resolves. What is wrong here?
async function getSet () {
//get settings from asyncstorage and setstate
}
async function pairs () {
//fetch data and set another state
}
async function fetchIt () {
//fetch another data and set a third state
}
useEffect(() => {
getSet()
.then(pairs())
.then(fetchIt())
.then(() => {
setLoading(false);
});
}, []);
fetchIt() is running before pairs()
The calls aren't chained properly. To make it simpler use async await:
useEffect(() => {
(async function () {
await getSet();
await pairs();
await fetchIt();
setLoading(false);
})();
}, []);
If each call depends on the result of the last it looks like
const r1 = await getSet();
const r2 = await pairs(r1);
// etcetera
You haven't chained the 'then's properly. They must return a promise as well. Read more here.
const getSet = async() => {
//get settings from asyncstorage and setstate
return new Promise((resolve, reject) => {
resolve('getSet');
});
}
const pairs = async() => {
//fetch data and set another state
return new Promise((resolve, reject) => {
resolve('pairs');
});
}
const fetchIt = async() => {
//fetch another data and set a third state
return new Promise((resolve, reject) => {
resolve('fetchIt');
});
}
getSet()
.then(response => {
console.log(response);
return pairs();
})
.then(response => {
console.log(response);
return fetchIt();
})
.then(response => {
console.log(response);
// setLoading(false);
});

How to use multiply times async function in mocha tests

I would like to use multiply times async foo function in mocha tests and try to do it like this:
describe('This test', () => {
const foo = async () => {
const wrapper = mount(Component);
const button = wrapper.find('button');
button.simulate('click');
wrapper.update();
await new Promise(res => setTimeout(() => {
res('');
}, 0));
wrapper.update();
return wrapper;
};
it('should pass 1', () => {
const wrapper = foo();
console.log(wrapper.debug());
});
it('should pass 2', () => {
const wrapper = foo();
console.log(wrapper.debug());
});
});
But then output is
TypeError: wrapper.debug is not a function
This works fine without async await.
Can you help resolve it?
It looks like solution is:
describe('This test', () => {
const foo = async () => {
const wrapper = mount(Component);
const button = wrapper.find('button');
button.simulate('click');
wrapper.update();
await new Promise(res => setTimeout(() => {
res('');
}, 0));
wrapper.update();
return wrapper;
};
it('should pass 1', async () => {
await foo();
console.log(wrapper.debug());
});
it('should pass 2', async () => {
await = foo();
console.log(wrapper.debug());
});
});
#CodeJoe is correct,
use async await for resolving this. async always returns a promise, so await functions are there to wait for those promises to resolve or reject.
If some thing is returned from async functions, then await keyword is used to call that function and wait for the promise to resolve or reject. await is always meant for WAIT
describe('This test', () => {
const foo = async () => {
return new Promise(async (resolve, reject) => {
const wrapper = mount(Component);
const button = wrapper.find('button');
button.simulate('click');
wrapper.update();
await new Promise(res => setTimeout(() => {
res('');
}, 0));
wrapper.update();
resolve(wrapper);
})
};
it('should pass 1', () => {
const wrapper = await foo();
console.log(wrapper.debug());
});
it('should pass 2', () => {
const wrapper = await foo();
console.log(wrapper.debug());
});
});
Also, always try to return something from your async function to avoid resolve conflicts. If there is not something to resolve, make a new promise and return resolve.
In promises instances/ async promises, use the following according to requirement -
return resolve()
return resolve(wrapper)
return reject()
return reject('Reject message')
return reject(new Error('Error message')

How can I avoid using another promise in my case?

I have two .js files like:
index.js:
app.get("/testing", (req, res) => {
testAsyncFunction().then((resolvedData) => {
res.send(resolvedData);
});
})
and server.js:
const asyncFunction = () => {
return new Promise((res) => {
setTimeout(() => {
res('resolved');
},3000 )
})
}
const testAsyncFunction = () => {
return new Promise(async (res) => {
const result = await asyncFunction();
return res(result);
})
}
and this is working as intended but if I change the testAsyncFunction(so that I don't create a new promise) to something like this:
const testAsyncFunction = async () => {
const result = await asyncFunction();
return result;
}
and in index.js:
app.get("/testing", (req, res) => {
res.send(testAsyncFunction());
})
I'm getting an empty object because it isn't waiting for 3 seconds, what am I missing in the latter case? I want to avoid creating a new Promise just to wait for another promise.
UPDATE
I changed the testAsyncFunctionto something like this:
const testAsyncFunction = () => {
asyncFunction().then((result) => {
return result;
})
}
Even though the above function. isn't an async function why do I still have to wait for it in the index.js..I'm assuming the returned value won't be a promise in this case so that's the part I'm confused about.
so that I don't create a new promise that's how it should be, other way is an antipattern. However, when a function returns a promise you need to wait for the same
app.get("/testing", async (req, res) => {
let obj = await testAsyncFunction()
res.send(obj );
});
OR
app.get("/testing", (req, res) => {
testAsyncFunction().then((obj) => {
res.send(obj );
})
});
const testAsyncFunction = async () => {
const result = await asyncFunction();
return result;
}
async functions always return promises. So this is equivalent to:
const testAsyncFunction = () => {
return asyncFunction();
}
I want to avoid creating a new Promise just to wait for another promise.
So just use the existing promise:
app.get("/testing", (req, res) => {
asyncFunction().then((resolvedData) => {
res.send(resolvedData);
});
})
const asyncFunction = () => {
return new Promise((res) => {
setTimeout(() => {
res('resolved');
},3000 )
})
}

Is it safe to resolve a promise multiple times?

I have an i18n service in my application which contains the following code:
var i18nService = function() {
this.ensureLocaleIsLoaded = function() {
if( !this.existingPromise ) {
this.existingPromise = $q.defer();
var deferred = this.existingPromise;
var userLanguage = $( "body" ).data( "language" );
this.userLanguage = userLanguage;
console.log( "Loading locale '" + userLanguage + "' from server..." );
$http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
$rootScope.i18n = translations;
deferred.resolve( $rootScope.i18n );
} );
}
if( $rootScope.i18n ) {
this.existingPromise.resolve( $rootScope.i18n );
}
return this.existingPromise.promise;
};
The idea is that the user would call ensureLocaleIsLoaded and wait for the promise to be resolved. But given that the purpose of the function is to only ensure that the locale is loaded, it would be perfectly fine for the user to invoke it several times.
I'm currently just storing a single promise and resolve it if the user calls the function again after the locale has been successfully retrieved from the server.
From what I can tell, this is working as intended, but I'm wondering if this is a proper approach.
As I understand promises at present, this should be 100% fine. The only thing to understand is that once resolved (or rejected), that is it for a defered object - it is done.
If you call then(...) on its promise again, you immediately get the (first) resolved/rejected result.
Additional calls to resolve() will not have any effect.
Below is an executable snippet that covers those use cases:
var p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
resolve(3);
});
p.then(x => console.log('resolved to ' + x))
.catch(x => console.log('never called ' + x));
p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));
I faced the same thing a while ago, indeed a promise can be only resolved once, another tries will do nothing (no error, no warning, no then invocation).
I decided to work it around like this:
getUsers(users => showThem(users));
getUsers(callback){
callback(getCachedUsers())
api.getUsers().then(users => callback(users))
}
just pass your function as a callback and invoke it as many times you wish! Hope that makes sense.
There s no clear way to resolve promises multiple times because since it's resolved it's done. The better approach here is to use observer-observable pattern for example i wrote following code that observes socket client event. You can extend this code to met your need
const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
const Observable = function (fn) {
const subscribers = [];
this.subscribe = subscribers.push.bind(subscribers);
const observer = {
next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
};
setTimeout(() => {
try {
fn(observer);
} catch (e) {
subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
}
});
};
const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
fromEvent(client, 'document:save').subscribe({
async next(document, docName) {
await writeFilePromise(resolve(dataDir, `${docName}`), document);
client.emit('document:save', document);
}
});
If you need to change the return value of promise, simply return new value in then and chain next then/catch on it
var p1 = new Promise((resolve, reject) => { resolve(1) });
var p2 = p1.then(v => {
console.log("First then, value is", v);
return 2;
});
p2.then(v => {
console.log("Second then, value is", v);
});
You can write tests to confirm the behavior.
By running the following test you can conclude that
The resolve()/reject() call never throw error.
Once settled (rejected), the resolved value (rejected error) will be preserved
regardless of following resolve() or reject() calls.
You can also check my blog post for details.
/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default
describe('promise', () => {
test('error catch with resolve', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise(resolve => {
try {
resolve()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
throw new Error('error thrown out side')
} catch (e) {
rs('error caught in expected location')
}
}))
test('error catch with reject', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise((_resolve, reject) => {
try {
reject()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
} catch (e) {
try {
throw new Error('error thrown out side')
} catch (e){
rs('error caught in expected location')
}
}
}))
test('await multiple times resolved promise', async () => {
const pr = Promise.resolve(1)
expect(await pr).toBe(1)
expect(await pr).toBe(1)
})
test('await multiple times rejected promise', async () => {
const pr = Promise.reject(1)
expect(await flipPromise(pr)).toBe(1)
expect(await flipPromise(pr)).toBe(1)
})
test('resolve multiple times', async () => {
const pr = new Promise(resolve => {
resolve(1)
resolve(2)
resolve(3)
})
expect(await pr).toBe(1)
})
test('resolve then reject', async () => {
const pr = new Promise((resolve, reject) => {
resolve(1)
resolve(2)
resolve(3)
reject(4)
})
expect(await pr).toBe(1)
})
test('reject multiple times', async () => {
const pr = new Promise((_resolve, reject) => {
reject(1)
reject(2)
reject(3)
})
expect(await flipPromise(pr)).toBe(1)
})
test('reject then resolve', async () => {
const pr = new Promise((resolve, reject) => {
reject(1)
reject(2)
reject(3)
resolve(4)
})
expect(await flipPromise(pr)).toBe(1)
})
test('constructor is not async', async () => {
let val
let val1
const pr = new Promise(resolve => {
val = 1
setTimeout(() => {
resolve()
val1 = 2
})
})
expect(val).toBe(1)
expect(val1).toBeUndefined()
await pr
expect(val).toBe(1)
expect(val1).toBe(2)
})
})
What you should do is put an ng-if on your main ng-outlet and show a loading spinner instead. Once your locale is loaded the you show the outlet and let the component hierarchy render. This way all of your application can assume that the locale is loaded and no checks are necessary.
No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.
There is pattern that can be used to trace such issues in debug time. Great lecture on this topic: Ruben Bridgewater — Error handling: doing it right! (the part related to the question is around 40 min)
see github gist: reuse_promise.js
/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/
import React, { useEffect, useState } from 'react'
export default () => {
const [somePromise, setSomePromise] = useState(promiseCreator())
useEffect(() => {
somePromise.then(data => {
// do things here
setSomePromise(promiseCreator())
})
}, [somePromise])
}
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// do things
resolve(/*data*/)
})
}

Categories