I'm trying to implement my own promise in JavaScript. Below is my class.
const states = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected"
}
class MyPromise {
constructor(computation) {
this.state = states.PENDING
this.value = undefined;
this.error = undefined;
this.thenQueue = [];
if(typeof computation === 'function') {
setTimeout(() => {
try {
computation(
this.onFulFilled.bind(this),
this.onReject.bind(this)
);
} catch(ex) {
this.onReject.bind(this);
}
});
}
}
then = (fullfilledFn, catchFn) => {
const promise = new MyPromise();
this.thenQueue.push([promise, fullfilledFn, catchFn]);
if(this.state === states.FULFILLED) {
this.propageFulFilled()
} else if(this.state == states.REJECTED) {
this.propageRejected();
}
}
catch = (catchFn) => {
return this.then(undefined, catchFn);
}
onFulFilled = (value) => {
if(this.state === states.PENDING) {
this.state = states.FULFILLED;
this.value = value;
this.propageFulFilled();
}
}
onReject = (error) => {
if(this.state === states.PENDING) {
this.state = states.REJECTED;
this.error = error;
this.propageRejected();
}
}
propageFulFilled = () => {
for(const [promise, fullFilledFn] of this.thenQueue) {
const result = fullFilledFn(this.value);
if(typeof result === 'MyPromise') {
promise.then(
value => promise.onFulFilled(value),
error => promise.onReject(error)
)
} else {
promise.onFulFilled(result); // final result
}
}
this.thenQueue = [];
}
propageRejected = () => {
for(const [promise, undefined, catchFn] of this.thenQueue) {
const result = catchFn(this.value);
if(typeof result === 'MyPromise') {
promise.then(
value => promise.onFulFilled(value),
error => promise.onReject(error)
)
} else {
promise.onFulFilled(result); // final result
}
}
this.thenQueue = [];
}
}
If I call the code below, it works fine
const testPromise = new MyPromise((resolve, reject) => {
setTimeout(() => resolve(10));
});
const firstCall = testPromise.then((value) => {
console.log(value)
return value+1;
});
However, if I add a second then to my firstCall request as below:
const firstCall = testPromise.then((value) => {
console.log(value)
return value+1;
}).then((newVal) => {
console.log(newVal)
});
I got the error TypeError: Cannot read property 'then' of undefined. Does anyone know why it is happening?
Thanks
Your then function is not returning anything:
then = (fullfilledFn, catchFn) => {
const promise = new MyPromise();
this.thenQueue.push([promise, fullfilledFn, catchFn]);
if(this.state === states.FULFILLED) {
this.propageFulFilled()
} else if(this.state == states.REJECTED) {
this.propageRejected();
}
return promise;
}
Related
this is my code:
const sendBoardId = async boardId => {
let result;
try {
result = await axios.get(`${fetchConfig.prefix}/game/get_board_by_id`, {
params: {
boardId,
},
});
} catch (e) {
console.log(e);
}
console.log('board:', result.data);
if (result.data == null || result.data.length == 0 || result.data == undefined) {
const BoardData = [];
return BoardData;
}
return result.data;
};
const Board = sendBoardId(1);
console.log('aaa', Board);
export { Board };
In the "aaa" console log I keep getting promise, and only in the promise result I got what I needed,
I want to export the promise result and not all the promise it self.
How should I do it?
To use Promise
const sendBoardId = async (boardId) =>
new Promise(async (resolve, reject) => {
let result;
try {
result = await axios.get(`${fetchConfig.prefix}/game/get_board_by_id`, {
params: {
boardId,
},
});
} catch (e) {
console.log(e);
reject(e);
}
console.log("board:", result.data);
if (
result.data === null ||
result.data.length === 0 ||
result.data === undefined
) {
resolve([]);
}
resolve(result.data);
});
const Board = sendBoardId; // UseLess
console.log("aaa", Board);
export { Board }; // Better to direct export sendBoardId
And use:
import {Board} from "...........";
const xyz = async() =>{
const data = await Board(1);
}
I am trying to integrate debounce to a search input so i can search through fetched data (after getting the data ) with the full term, i have this debounce function in the App.js :
debounce = (callback, delay) => {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => callback(...args), delay);
}
}
debouncedTerm = this.debounce(text => console.log(text), 300);
I have also this method in the App.js
onSearchChanged = data => {
const searchBar = data.target.value.toLowerCase();
this.debouncedTerm(searchBar)
this.setState({
searchBar: searchBar,
}, () => {
let newData = {...this.state};
if( typeof this.props.source !== "undefined"){
newData.source = this.props.source
}
newData.params = {
searchBar : this.state.searchBar
}
Api.fetchQuestions(newData.params)
.catch( error => {
if (typeof error.message !== "undefined"){
newData.config.formMessage = {
message : error.message,
success : false,
}
newData.widgetStatus = false;
this.setState(newData)
}
return {data:{data:[]}}
})
.then( response => {
if(response.data.status === 'success'){
return response.data.data;
}
this.setState({
questions: [...response.data.questions.items],
loading: false
});
})
});
};
FYI : I pass the value of the input (searchBar) as a parameter in newData.params
'URL/questions?client_id='+params.client_id+'&term='+params.searchBar,
How can i integrate debouncedTerm in the method onSearchChanged
I have the search input in a child component
<InputSearch
type="search"
placeholder="Search"
onChange={props.searchCallback}
/>
this is a part of the code I wrote recently:
export function debounceFetch(fn, delay) {
let createdTimeout;
let controller;
function onCancel() {
controller?.abort();
createdTimeout?.cancel();
}
async function debounced(...args) {
onCancel();
controller = new AbortController();
createdTimeout = timeout(delay);
await createdTimeout.delay;
return fn(...args, controller.signal);
}
return [
debounced,
onCancel,
];
}
It cancels the fetch request too,
and this is the timeout part:
function timeout(ms) {
let timeoutRef;
let rejectRef;
const delay = new Promise((resolve, reject) => {
timeoutRef = setTimeout(() => {
resolve('timeout done');
}, ms);
rejectRef = () => {
reject(new Error('cancel timeout'));
};
});
return {
delay,
cancel() {
clearTimeout(timeoutRef);
rejectRef();
},
};
}
First of all I am a newbie in nodejs, please be indulgent :)
I have a problem with some async functions. In my case, a first API call is made and then a sub call is made on every item returned. this is my code after the first call is made :
const countryStatesCalls: Promise<any>[] = [];
countries.forEach((country, index) => {
countries[index] = convertToCountry(country);
const countryStatesCall = (countryId: number) => {
return new Promise((resolve, reject) => {
const countryStatesList = getStateCountries(countryId);
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
resolve([]);
} else {
resolve(countryStatesList);
}
} else {
reject([]);
}
});
};
countryStatesCalls.push(
countryStatesCall(countries[index].id).then(countryStates => {
countries[index].states = countryStates;
}).catch(countryStatesType => {
countries[index].states = countryStatesType;
})
);
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});
the following code
res.json(countries)
is supposed to be executed after all promises execution has been finished.
EDIT : the getStateCountries async function :
const getStateCountries = async (countryId: number, lang?: string) => {
const listState = await odoo.searchRead(
'res.country.state',
{
filters: [
{
field: 'country_id',
operator: '=',
value: countryId,
},
],
fields: fieldState,
},
{ lang }
);
if (!listState.length) {
return [];
} else {
listState.forEach((state, index) => {
listState[index] = convertToState(state);
});
return listState;
}
};
the main function is an express controller
I think my mistake may be obvious but can you help me please, I read many topics and I cannot see what am I doing wrong.
Thank you in advance
[SOLUTION]
As pointed out by comments, I was doing it the wrong way around, my call on promise called another promise: the solution was to write :
const countryStatesCalls: Promise<any>[] = [];
for (let index = 0; index < countries.length; index++) {
countries[index] = convertToCountry(countries[index]);
if (withSates) {
countryStatesCalls.push(
getStateCountries(countries[index].id)
);
} else {
countries[index].states = [];
}
}
if (withSates) {
await Promise.all(countryStatesCalls).then((countriesStates) => {
countries.forEach((country, index) => {
country.states = [];
for (const countryStates of countriesStates) {
if (countryStates.length
&& countryStates[0].country_id.code === country.id) {
countries[index].states = countryStates;
}
}
});
res.json(countries);
});
} else {
res.json(countries);
}
thank you everyone
I can't exactly tell why it doesn't work, but this looks a bit like the Promise constructor antipattern. The getStateCountries function is async and already creates a promise, so you need to await it or chain .then() to it. The new Promise is not necessary.
Also I'd recommend to avoid forEach+push, rather just use map, and get rid of the countryStatesCall helper:
const countryStatesCalls: Promise<any>[] = countries.map((country, index) => {
countries[index] = convertToCountry(country);
return getStateCountries(countries[index].id).then(countryStatesList => {
if (countryStatesList) {
if (Object.prototype.toString.call(countryStatesList) !== '[object Array]') {
return [];
} else {
return countryStatesList;
}
} else {
throw new Error("getStateCountries() resolved to nothing");
}
}).then(countryStates => {
countries[index].states = countryStates;
});
});
await Promise.all(countryStatesCalls).then(_ => {
res.json(countries)
});
I am trying to create a simple custom promise from scratch.
For some reason, then is being executed before the onResolve function is called.
Because of this, the response variable is an empty string.
Where am I going wrong here?
Index.js
import CustomPromise from "./customPromise";
const makeApiCall = () => {
return new CustomPromise((success, failure) => {
setTimeout(() => {
let apiResponse = { statusCode: 200, response: "hello" };
if (apiResponse.statusCode == 200) {
success(apiResponse);
} else {
failure(apiResponse);
}
}, 1000);
});
};
makeApiCall().then(response => {
console.log(response);
});
CustomPromise.js
export default class CustomPromise {
constructor(executorFunc) {
this.onResolve = this.onResolve.bind(this);
this.onReject = this.onReject.bind(this);
this.response = "";
executorFunc(this.onResolve, this.onReject);
}
then(input) {
input(this.response);
}
onResolve(response) {
this.response = response;
}
onReject(input) {
input();
}
}
When calling CustomPromise.then you can see that you are just taking the continuation input and calling it immediately regardless of the state of the promise. I think what you'd want to do is to create a task queue and on resolve, you would go ahead and execute it.
You are missing a few key things as well.
First you need to maintain a state inside the promise so that you know when a promise has been settled. This makes it so that you cant resolve/reject the promise multiple times.
Your reject function probably shouldn't just be executing the input. You typically pass a reason into the rejection which will then execute all catch tasks.
No chaining support. Not sure that was even your goal.
const functionOrNoop = (fn) => {
let result = () => {};
if (typeof fn === "function") {
result = fn;
}
return result;
};
class CustomPromise {
constructor(executor) {
this.queue = [];
this.state = "pending";
this.value = null;
this.reason = null;
executor(this.onResolve, this.onReject);
}
static reject(reason) {
return new CustomPromise((_, reject) => reject(reason));
}
static resolve(value) {
return new CustomPromise((resolve) => resolve(value));
}
then(fn) {
return new CustomPromise((resolve, reject) => {
const resolved = (value) => {
try {
resolve(fn(value))
} catch (e) {
reject(e);
}
};
this.enqueue({
resolved,
rejected: reject
});
});
}
catch (fn) {
return new CustomPromise((resolve, reject) => {
const rejected = (reason) => {
try {
resolve(fn(reason))
} catch (e) {
reject(e);
}
};
this.enqueue({
rejected
});
});
}
onResolve = (value) => {
if (this.state === "pending") {
this.state = "resolved";
this.value = value;
this.finalize();
}
}
onReject = (reason) => {
if (this.state === "pending") {
this.state = "rejected";
this.reason = reason;
this.finalize();
}
};
enqueue(task) {
if (this.state === "pending") {
this.queue.push(task);
} else {
this.eval(task);
}
}
eval(task) {
if (this.state === "resolved") {
functionOrNoop(task.resolved)(this.value);
} else if (this.state === "rejected") {
functionOrNoop(task.rejected)(this.reason);
}
}
finalize() {
this.queue.forEach((task) => this.eval(task));
this.queue = [];
}
}
const p = CustomPromise.resolve("hello")
p
.then((value) => value.toUpperCase())
.then((value) => `J${value.slice(1)}`)
.then((value) => console.log(value))
p.then((value) => console.log(value));
p
.then(() => {
throw new Error(":(")
})
.catch((e) => console.log(e.message))
.then(() => {
throw new Error(":)")
})
.then(() => console.log("SHOULD NOT CALL!"))
.catch((e) => console.log(e.message));
This is meant to serve as an example(so expect bugs) but it encapsulates some of the things I mentioned above. Promises are really complex and you are missing things like 'microtasking' and tens of thousands of hours to testing, development, and vetting.
I'm having yet another async issue where I'm lost and have no idea where or how to fix it. Forgive my bad naming.
api call to twitch api and returns an array its results.
exports.batchPromiseWrapper = function(arr) {
const filteredMungedDataArr = [];
let promiseBatachArray = arr.map(vod_id => {
var url = `https://api.twitch.tv/kraken/videos/${vod_id.id}/markers`;
var params = { api_version: 5 };
return axios
.get(url, {
params: params,
headers: {
"Client-ID": "xxxxxxxxxxxxxxx"
}
})
.then(res => {
return res.data;
})
.catch(function(error) {
console.log(error);
});
});
return Promise.all(promiseBatachArray)
.then(markers => {
if (markers !== null) {
markers.map(markerObj => {
if (markerObj.markers.game_changes !== null) {
markerObj.markers.game_changes.forEach(gameName => {
if (gameName.label === "Fortnite") {
filteredMungedDataArr.push(markerObj);
}
});
}
});
return filteredMungedDataArr;
}
})
.catch(err => {
if (err.status === 500 || err.status === 404) {
console.log("error: ", err, err.message);
}
});
};
The data looks like this:
[[1,2,3,4,5],[1,2,3,4,5]], generator will yield and make a promise.all call of 5 before pausing 5sec and continuing to the next batch of 5.
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (v, t) => {
return new Promise(resolve => {
setTimeout(resolve.bind(null, v), t);
});
};
if (!evalNextValue.done) {
exports.batchPromiseWrapper(evalNextValue.value).then(data => {
let newBatchArray = batchArray;
if (data !== undefined) {
newBatchArray = batchArray.concat(data);
}
delay(5000).then(() => {
exports.batchFetchingGeneratorWrapper(generator, newBatchArray);
});
});
} else {
console.log("yay done!", batchArray);
return batchArray;
}
};
I'm able to console the results in batchArray from batchFetchingGeneratorWrapper, but I unable to act on it and I know it has something to do with async and how it has yet to be resolved.
promiseDataWrapper
.then(data => {
return gatherData.cleanUpVODData(data);
})
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
let batchArrResult = [];
let g = batchFetching(data);
new Promise((resolve, reject) => {
gatherData.batchFetchingGeneratorWrapper(g, batchArrResult);
if (g.done) { // i dont think this works
console.log("batchArrResult 1: ", batchArrResult);
resolve(batchArrResult);
}
}).then(result => console.log("asdfasdf", batchArrResult)); // empty array is returned
});
As far as I can tell, the problem lies chiefly in batchFetchingGeneratorWrapper().
It should be a matter of :
fixing delay()
making appropriate returns to make the recursion work
ensuring that the function returns Promise.
Almost undoubtedly (syntactically) simpler with async/await but here it is with old-fashioned thens :
exports.batchFetchingGeneratorWrapper = function(generator, batchArray) {
let evalNextValue = generator.next();
let delay = (t) => {
return new Promise(resolve => {
setTimeout(resolve, t);
});
};
if (!evalNextValue.done) {
return exports.batchPromiseWrapper(evalNextValue.value).then(data => {
return delay(5000).then(() => {
return exports.batchFetchingGeneratorWrapper(generator, batchArray.concat(data || []));
});
});
} else {
console.log("yay done!", batchArray);
return Promise.resolve(batchArray); // <<< promise wrapped to ensure that batchFetchingGeneratorWrapper() returns Promise
}
};
And chain the batchFetchingGeneratorWrapper() call appropriately :
promiseDataWrapper
.then(data => gatherData.cleanUpVODData(data))
.then(data => {
function* batchFetching(batchArray) {
for (let i = 0; i < batchArray.length; i++) {
yield batchArray[i];
}
}
return gatherData.batchFetchingGeneratorWrapper(batchFetching(data), []).then(batchArrResult => {
console.log('batchArrResult: ', batchArrResult);
return batchArrResult;
});
}).catch(error => {
console.log(error);
});