Welcome my friends!
I am Cory
And I am learning Scottish Gaelic
And now, you are too!
I am a staff software engineer at Aumni
( now a JPMorgan company )
I am delighted to be here with you all today.
And I am excited to present to you...
(this will be relevant later)
(for me)
First off, async/await is promises!
the author [is] extrapolating from his own lack of familiarity
I think some authors need to be more humble
This article is bad, bad advice.
This...
async () => {}
...is much nicer than...
() => new Promise((resolve) => resolve())
The fact that this comment has several more claps [than the article] says a lot.
I feel bad for the author.
may make for a better pattern than
In many cases
Async/Await patterns can at times provide wrong or inadequate signals that we are working with asynchronous code.
Async/Await incurs a high cost to doing the right thing. It encourages bad practices like "fire and forget", which neglects error handling.
Even if you lean as far as possible into async/await, you will still need to use Promises directly at times.
So, so many comparisons of async/await vs Promises are not comparing apples to apples, or are rigging the examples.
You cannot escape the fact that you are working with promises when you have to then/catch.
Promises have a lower cost to doing the right thing, making things like error handling and scope narrowing easier.
Promises are a complete abstraction for async code. One does not have to rely on other structures or paradigms to do anything that needs to be done.
Promises stealth teach you about functors and monads and other patterns that provide better guarantees, better reasoning, and fewer complications.
const howManyTruthy = async (arr) => {
const truthyValues = arr.filter(Boolean)
return truthyValues.length;
}
howManyTruthy(['', 0, 0n, false, undefined, Symbol(), null]) // 🤷♂️
What does `howManyTruthy` return?
1
Promise<1>
Promises
What does `howManyTruthy` return?
1
Promise<1>
const howManyTruthy = (arr) => {
const truthyValues = arr.filter(Boolean)
return Promise.resolve(truthyValues.length);
}
howManyTruthy(['', 0, 0n, false, undefined, Symbol(), null]) // 🤷♂️
const getData = async (userId) => {
const res = await fetch(`/data/${id}`);
return res.json());
}
async/await
Promises
const getData = (userId) => fetch(`/data/${id}`)
.then(res => res.json());
const getData = async (userId) => {
try {
res = await fetch(`/data/${id}`);
return res.json()
} catch (e) {
return {};
}
}
const getData = (userId) => fetch(`/data/${id}`)
.then(res => res.json())
.catch((e) => ({}));
Async/await imposes a stiff syntactic penalty on error handling.
The predictable result is that when times lines and pressures conspire, many devs wont deal with errors.
async/await + promises
const getData = async (userId) => await fetch(`/data/${id}`)
.json()
.catch((e) => ({}));
You still have to directly use promises. 🤷♂️
Which leads us directly into the next point.
Async/await is an incomplete abstraction. We are forced to live in two worlds at the same time. Sometimes async/await, sometimes Promise chaining.
Maybe this is fine. It's certainly managable in isolation, but programming is hard. The fewer things we have to context switch on, generally, the better.
const processData = ({ userData, sessionPreferences }) => {
save('userData', userData);
save('session', sessionPreferences);
return { userData, sessionPreferences }
}
const processData = async ({ userData, sessionPreferences }) => {
await save('userData', userData);
await save('session', sessionPreferences);
return { userData, sessionPreferences }
}
const processData = ({ userData, sessionPreferences }) =>
save('userData', userData)
.then(() => save('session', sessionPreferences))
.then(() => ({ userData, sessionPreferences })
Sync
async/await
Promises
const processData = ({ userData, sessionPreferences }) => {
save('userData', userData);
save('session', sessionPreferences);
return { userData, sessionPreferences };
}
const processData = async ({ userData, sessionPreferences }) => {
await Promise.all([
await save('userData', userData),
await save('session', sessionPreferences),
]);
return { userData, sessionPreferences };
}
const processData = ({ userData, sessionPreferences }) =>
Promise.all([
save('userData', userData)
save('session', sessionPreferences)
]).then(() => ({ userData, sessionPreferences });
Sync
async/await
Promises
const processData = async ({ userData, sessionPreferences }) => {
await Promise.all([
save('userData', userData),
save('session', sessionPreferences),
]);
return { userData, sessionPreferences };
}
Sometimes, we can't or shouldn't use the async/await key words when working with async functions dealing with async actions. 🤨
Context switching within the same code space is just one more thing we have to manage when ever we use async/await.
Promises are a complete abastraction over asynchronous actions. When using promises and promise chainging, we are never not doing that. One less thing to have to context switch on. 🥰
function loadJson(url) {
return fetch(url)
.then(response => {
if (response.status == 200) {
return response.json();
} else {
throw new Error(response.status);
}
});
}
async function loadJson(url) {
let response = await fetch(url);
if (response.status == 200) {
let json = await response.json();
return json;
}
throw new Error(response.status);
}
Credit https://javascript.info/task/rewrite-async
Assignment:
Solution:
🤔
🤨 huh?
const loadJson = (url) => fetch(url)
.then(response => response.ok
? response.json()
: Promise.reject(response));
const loadJson = async (url) => {
const response = await fetch(url);
if (response.ok) return response.json();
throw new Error(response.status);
}
Credit https://javascript.info/task/rewrite-async
Credit https://javascript.info/task/rewrite-async
Assignment:
Solution:
😇
😆 better
const loadJson = async (url) => {
const response = await fetch(url);
if (response.ok) return response.json();
throw response;
}
This...
async () => {}
...is much nicer than...
() => new Promise((resolve) => resolve())
() => new Promise((resolve) => resolve())
async () => {}
Assignment:
Solution:
🥸
🤦♂️
Promise.resolve
🫰
validateForm()
.then(isFormValid => isFormValid || Promise.reject(new Error(FormStatus.INCOMPLETE)))
.then(async () => {
const { investees, investors, hqInvestee, hqInvestor } = formatFormData(getFormValues());
const formattedInvestee = {
...investees,
industry_ids: investees.industries?.map(({ id }) => id) ?? [],
};
const formattedInvestor = {
...investors,
industry_ids: investors.industries?.map(({ id }) => id) ?? [],
};
const investeeHQ = Object.keys(hqInvestee).length ? hqInvestee : undefined;
const investorHQ = Object.keys(hqInvestor).length ? hqInvestor : undefined;
if (!formattedInvestee.id && !formattedInvestor.id) {
createLegalEntity(formattedInvestee, 'investee', investeeHQ)
.then(async investee => {
setInvestee({ ...formattedInvestee, id: investee.id });
const investor = await createLegalEntity(formattedInvestor, 'investor', investorHQ);
setInvestor({ ...formattedInvestor, id: investor.id });
return { investee, investor };
})
.then(async () => {
await submit();
})
.catch(error => showErrorMessage(error));
}
else if (!formattedInvestee.id) {
await createLegalEntity(formattedInvestee, 'investee', investeeHQ)
.then(investee => {
setInvestee('investee', { ...formattedInvestee, id: investee.id });
})
.then(async () => {
await submit();
})
.catch(error => showErrorMessage(error));
}
else if (!formattedInvestor.id) {
await createLegalEntity(formattedInvestor, 'investor', investorHQ)
.then(investor => {
setInvestor({ ...formattedInvestor, id: investor.id });
})
.then(async () => {
await submit();
})
.catch(error => showErrorMessage(error));
}
else {
await submit();
}
})
.catch(error => {
if (error.message === FormStatus.INCOMPLETE) {
showErrorMessage(error);
} else {
reportError(error);
showErrorMessage({ message: 'There was an adding the record, please try again.' });
}
});
try {
await const isValidForm = validateForm()
if (!isFormValid) throw new Error(FormStatus.INCOMPLETE)
const { investees, investors, hqInvestee, hqInvestor } = formatFormData(getFormValues());
const formattedInvestee = {
...investees,
industry_ids: investees.industries?.map(({ id }) => id) ?? [],
};
const formattedInvestor = {
...investors,
industry_ids: investors.industries?.map(({ id }) => id) ?? [],
};
const investeeHQ = Object.keys(hqInvestee).length ? hqInvestee : undefined;
const investorHQ = Object.keys(hqInvestor).length ? hqInvestor : undefined;
if (!formattedInvestee.id && !formattedInvestor.id) {
try {
const investee = await createLegalEntity(formattedInvestee, 'investee', investeeHQ)
setInvestee({ ...formattedInvestee, id: investee.id });
const investor = await createLegalEntity(formattedInvestor, 'investor', investorHQ);
setInvestor({ ...formattedInvestor, id: investor.id });
await submit();
} catch (error) {
showErrorMessage(error)
}
}
else if (!formattedInvestee.id) {
try {
const investee = await createLegalEntity(formattedInvestee, 'investee', investeeHQ)
setInvestee('investee', { ...formattedInvestee, id: investee.id });
await submit();
} catch (error) {
showErrorMessage(error)
}
}
else if (!formattedInvestor.id) {
try {
const investor = await createLegalEntity(formattedInvestor, 'investor', investorHQ)
setInvestor({ ...formattedInvestor, id: investor.id });
await submit();
} catch (error) {
showErrorMessage(error)
}
}
else {
await submit();
}
} catch (error) {
if (error.message === FormStatus.INCOMPLETE) {
showErrorMessage(error);
} else {
reportError(error);
showErrorMessage({ message: 'There was an adding the record, please try again.' });
}
}
const rejectWithWhen = rejection => predicate => ctx => predicate(ctx) ? rejection(ctx) : ctx;
const rejectWithIncompleteWhen = rejectWithWhen(new Error(FormStatus.INCOMPLETE));
validateForm()
.then(rejectWithIncompleteWhen(isFormValid => !isFormValid))
.then(() => formatFormData(getFormValues()))
.then(({ investees, investors, hqInvestee, hqInvestor }) => ({
investee: { ...investees, industry_ids: investees.industries?.map(get('id')) ?? [] },
investor: { ...investors, industry_ids: investors.industries?.map(get('id')) ?? [] },
investeeHQ: Object.keys(hqInvestee).length ? hqInvestee : undefined,
investorHQ: Object.keys(hqInvestor).length ? hqInvestor : undefined,
}))
.then(({ investee, investor, investeeHQ, investorHQ }) => Promise.allSettled([
investee.id
? investee
: createLegalEntity(investee, 'investee', investeeHQ).then(tap(({ id }) => setInvestee({ ...investee, id }))),
investor.id
? investor
: createLegalEntity(investor, 'investor', investorHQ).then(tap(({ id }) => setInvestor({ ...investor, id }))),
]))
.then(results =>
results.every(({ status }) => status === 'fulfilled')
? submit()
: Promise.reject(results.filter(({ status }) => status === 'rejected')),
)
.catch(errors => {
errors.forEach(error => {
if (error.message !== FormStatus.INCOMPLETE) reportError(error);
showErrorMessage({
message:
error.message === FormStatus.INCOMPLETE ? error.message : 'There was an error adding the record, please try again.',
});
});
});
const rejectWithWhen = rejection => predicate => ctx => predicate(ctx) ? rejection(ctx) : ctx;
const rejectWithIncompleteWhen = rejectWithWhen(new Error(FormStatus.INCOMPLETE));
validateForm()
.then(rejectWithIncompleteWhen(isFormValid => !isFormValid))
.then(() => formatFormData(getFormValues()))
.then(({ investees, investors, hqInvestee, hqInvestor }) => ({
investee: { ...investees, industry_ids: investees.industries?.map(get('id')) ?? [] },
investor: { ...investors, industry_ids: investors.industries?.map(get('id')) ?? [] },
investeeHQ: Object.keys(hqInvestee).length ? hqInvestee : undefined,
investorHQ: Object.keys(hqInvestor).length ? hqInvestor : undefined,
}))
.then(({ investee, investor, investeeHQ, investorHQ }) => Promise.allSettled([
investee.id
? investee
: createLegalEntity(investee, 'investee', investeeHQ).then(tap(({ id }) => setInvestee({ ...investee, id }))),
investor.id
? investor
: createLegalEntity(investor, 'investor', investorHQ).then(tap(({ id }) => setInvestor({ ...investor, id }))),
]))
.then(results =>
results.every(({ status }) => status === 'fulfilled')
? submit()
: Promise.reject(results.filter(({ status }) => status === 'rejected')),
)
.catch(errors => {
errors
.map(tap(error => error.message !== FormStatus.INCOMPLETE && reportError(error)))
.map(tap(({ message }) => showErrorMessage({
message: message === FormStatus.INCOMPLETE ? message : 'There was an error adding the record.'
})));
});
validateForm()
.then(rejectWithIncompleteWhen(isFormValid => !isFormValid))
.then(() => formatFormData(getFormValues()))
.then(({ investees, investors, hqInvestee, hqInvestor }) => ({
formattedInvestee: { ...investees, industry_ids: investees.industries?.map(get('id')) ?? [] },
formattedInvestor: { ...investors, industry_ids: investors.industries?.map(get('id')) ?? [] },
investeeHQ: Object.keys(hqInvestee).length ? hqInvestee : undefined,
investorHQ: Object.keys(hqInvestor).length ? hqInvestor : undefined,
}))
.then(results =>
results.every(({ status }) => status === 'fulfilled')
? submit()
: Promise.reject(results.filter(({ status }) => status === 'rejected')),
)
.catch(errors => {
errors
.map(tap(error => error.message !== FormStatus.INCOMPLETE && reportError(error)))
.map(tap(({ message }) => showErrorMessage({
message: message === FormStatus.INCOMPLETE ? message : 'There was an error adding the record.'
})));
});
.then(({ investee, investor, investeeHQ, investorHQ }) => Promise.allSettled([
investee.id
? investee
: createLegalEntity(investee, 'investee', investeeHQ)
.then(tap(({ id }) => setInvestee({ ...investee, id }))),
investor.id
? investor
: createLegalEntity(investor, 'investor', investorHQ)
.then(tap(({ id }) => setInvestor({ ...investor, id }))),
]))
const rejectWithWhen = rejection => predicate => ctx => predicate(ctx) ? rejection(ctx) : ctx;
const rejectWithIncompleteWhen = rejectWithWhen(new Error(FormStatus.INCOMPLETE));
validateForm()
.then(rejectWithIncompleteWhen(isFormValid => !isFormValid))
.then(() => formatFormData(getFormValues()))
.then(({ investees, investors, hqInvestee, hqInvestor }) => ({
investee: { ...investees, industry_ids: investees.industries?.map(get('id')) ?? [] },
investor: { ...investors, industry_ids: investors.industries?.map(get('id')) ?? [] },
investeeHQ: Object.keys(hqInvestee).length ? hqInvestee : undefined,
investorHQ: Object.keys(hqInvestor).length ? hqInvestor : undefined,
}))
.then(({ investee, investor, investeeHQ, investorHQ }) => Promise.allSettled([
investee.id
? investee
: createLegalEntity(investee, 'investee', investeeHQ).then(tap(({ id }) => setInvestee({ ...investee, id }))),
investor.id
? investor
: createLegalEntity(investor, 'investor', investorHQ).then(tap(({ id }) => setInvestor({ ...investor, id }))),
]))
.then(results =>
results.every(({ status }) => status === 'fulfilled')
? submit()
: Promise.reject(results.filter(({ status }) => status === 'rejected')),
)
.catch(errors => {
errors
.map(tap(error => error.message !== FormStatus.INCOMPLETE && reportError(error)))
.map(tap(({ message }) => showErrorMessage({
message: message === FormStatus.INCOMPLETE ? message : 'There was an error adding the record.'
})));
});