Purpose of my code, is to fire fetchNumbers() (that fetches numbers from API) when a user scrolls bottom of the page, some kind of infinite scroll. I'm having issue with condition inside axios promise (then), because it fires two console.log outputs at the same time. Seems like condition is ignored at all.
Method i'm using:
methods: {
fetchNumbers (type = 'default', offset = 0, limit = 0) {
return axios.get(globalConfig.NUMBERS_URL)
.then((resp) => {
if (type === 'infinite') {
console.log('infinite fired')
} else {
console.log('default fired')
}
})
}
Mounted function (where i suspect the issue):
mounted () {
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight > document.documentElement.offsetHeight - 1
if (bottomOfWindow) {
this.fetchNumbers('infinite')
}
}
}
When i reload the page, or enter it, i'm getting 2 console outputs at the same time:
default fired
infinite fired
Sometimes the order is reversed.
UPDATE.
Methods that calling fetchNumbers()
async created () {
await this.fetchNumbers()
}
showCats (bool) {
this.showCategories = bool
if (!bool) {
this.category = [1]
} else {
this.category = []
}
this.isActive = true
this.fetchNumbers()
}
UPDATE 2.
Found the culprit - would post it in an answer.
UPDATE 3.
Issue is indeed with onscroll function. I have 3 pages in my APP: main page, numbers page, contact page. If i go to the numbers page (where onscroll function is mounted), then go to main or contact page, then this onscroll function is still attached and when i reach the bottom - it fires my api call, even if it's not the numbers page. Is it possible to limit this function only to numbers page?
I had to disable onscroll listener on destroy:
destroyed () {
window.onscroll = null
}
So when i visit the main or contact page, that listener won't be attached.
Also i had to move onscroll listener from mounted to created, because when it was in mounted () it was firing twice:
created () {
window.onscroll = () => {
let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight > document.documentElement.offsetHeight - 1
if (bottomOfWindow) {
this.isActive = true
this.fetchNumbers('infinite', counter++ * this.limit)
}
}
}
fetchNumbers is most definetly called twice.
Try log every context as type.
fetchNumbers (type = 'default', offset = 0, limit = 0) {
return axios.get(globalConfig.NUMBERS_URL)
.then((resp) => {
if (type === 'infinite') {
console.log(type,'infinite fired'); //this will always print 'infinite', 'inifinite fired'
} else {
console.log(type,'default fired'); //this will print the caller's name and 'default fired'
type = 'default'; //after logging reset type to 'default' to continue the execution
}
})
}
async created () {
await this.fetchNumbers('created');
}
showCats (bool) {
this.showCategories = bool
if (!bool) {
this.category = [1];
} else {
this.category = [];
}
this.isActive = true
this.fetchNumbers('showCats');
}
Either created or showCats is being fired for some reason at the same time as window.onscroll. The randomly changing order suggests a race condition between the two methods.
UPDATE
This should work, provided you nested the desired page in a div with id="number_page_div_id" (you may probably have to adjust that elements height and position):
created () {
let element = getElementById('numbers_page_div_id');
element.onscroll = () => {
let bottomOfWindow = element.scrollTop + window.innerHeight > element.offsetHeight - 1;
if (bottomOfWindow) {
this.fetchNumbers('infinite')
}
}
}
you may approach separate condition instead of if/else e.g.
.then((resp) => {
if (type === 'infinite') {
console.log('infinite fired')
}
if (type === 'default '){
console.log('default fired')
}
})
Related
I have a main file that acts like an API to manage the playing, resuming or a video.
main.js
const { mainModule } = require('process');
const { startVideo, pauseVideo, stopVideo } = require('./modules/video.js');
function init(payload) {
if(payload.type === 'play') {
handlePlayVideo(payload.data)
} else if(payload.type === 'pause') {
handlePauseVideo(payload.data);
} else if(payload.type === 'stop') {
handleStopVideo(payload.data);
}
}
function handlePlayVideo(data) {
startVideo(data).then(() => {
console.log('Video state: play');
});
}
function handlePauseVideo(data) {
pauseVideo().then(() => {
console.log('Video state: pause');
});
}
function handleStopVideo(data) {
stopVideo().then(() => {
console.log('Video state: stop');
});
}
I want to be able to use the data every second when the state(payload.type) is 'play'. That would start my interval, but I'm stuck on how to be able to pause it (then continue later from the same second), or stop it.
This is what I've tried:
video.js
module.exports = (function () {
let currentSecond = 0;
let videoIsPaused = false;
let videoIsStopped = false;
function startVideo(data) {
const videoTicker = setInterval(() => {
if(videoIsPaused || videoIsStopped) {
console.log('video is paused or stopped');
clearInterval(videoTicker);
if(videoIsStopped) currentSecond = 0;
}
parseVideo(data.someData, currentSecond);
if(currentSecond === 100) clearInterval(scenarioTicker);
currentSecond++;
}, 1000);
}
function pauseVideo() {
videoIsPaused = true;
}
function videoIsStopped() {
videoIsStopped = true;
}
function parseVideo(data) {
// do something (stream) the data every second, except when the video is paused, or stopped
}
})();
With this code, the startVideo method starts executing every second, but I cannot pause it.
Update:
The starting of the video has been started(tested) using cli (ex. node main.js), which started the execution of start video for let's say one minute. In the same time, I'm opening a second cmd window, where I try to call the pauseVideo of the first window. So I think that my issue is because of the separate window environments which acts like a different scope, and I assume that my calls cannot interact with each other in this way. Is there a better way of doing/testing this?
In your if statement for the paused/stopped condition, you're not stopping the execution of the rest of the function. Therefore, the parts below it, like increasing currentSecond will still be hit. Perhaps an early return is what you need:
if(videoIsPaused || videoIsStopped) {
console.log('video is paused or stopped');
clearInterval(videoTicker);
if(videoIsStopped) currentSecond = 0;
return;
}
https://jsfiddle.net/AlexThunders/k8s79zL5/18/
I'm trying to change self typing text from English to Russian when I click option in select:
let engType = [
'Never give up. ',
'You can win. '
]
let rusType = [
'Никогда не сдавайся. ',
'Ты можешь победить. '
]
page is loaded and this function gradually types letters:
function typeCharacters(phrases) {
if(phrases[count] !== undefined && phrases[count] !== null && phrases[count] !== "") {
let allLength = phrases[count].length
setInterval(() => {
if(phrases[count] !== undefined && typePar !== null) {
character = phrases[count].slice(ind,ind+1)
txt = document.createTextNode(character)
typePar.appendChild(txt)
ind++
let typeLength = typePar.textContent.length
if(typeLength === allLength) {
count++
ind = 0
if(phrases[count] !== undefined) {
allLength += phrases[count].length
}
}
}
},100)
}
}
typeCharacters(engType)
It works. But when I merely touch select button without even choosing language, I get nonsense paragraph with mixed letters in one or in both languages within the same paragraph:
function searchLang(choosenLang) {
//if choosen language coincides with one in Object:
if(languages[choosenLang]) {
allDataElements.forEach(element => {
//every property of choosen object/language
for(let x in languages[choosenLang]) {
//compare with element's data attribute
if(element.getAttribute('data') === x) {
//the same attribute changes iinerText in accordance with object
element.innerText = languages[choosenLang][x]
if(languages[choosenLang].changePhrases !== undefined) {
languages[choosenLang].changePhrases(choosenLang)
}
}
}
})
}
}
select.addEventListener('click', () => {
allLangOptions.forEach(option => {
if(option.selected === true) {
let lang = option.value
searchLang(lang)
}
})
})
and the result:
Никeгда не сд.вайся. Тыuможешь по едить. OR
Никогда нe up. айс
However for other html elements select button works right: only when I choose option but not click select itself.
I use changePhrases functions in object to change language in typing paragraph:
let languages = {
en: {
mPheadLIabout: 'About',
mPheadLIprojects: 'Projects',
mPheadLIcontacts: 'Contacts',
changePhrases: function() {
if(typePar !== null) {
typePar.textContent = "";
count = 0
ind = 0
typeCharacters(engType)
}
}
},
ru: {
mPheadLIabout: 'О сайте',
mPheadLIprojects: 'Проекты',
mPheadLIcontacts: 'Контакты',
changePhrases: function() {
if(typePar !== null) {
typePar.textContent = "";
count = 0
ind = 0
typeCharacters(rusType)
}
}
}
}
At first paragraph clears itself, and begins to type from first character as indicated above.
I've tried to use variable reset to stop invoking typing English characters but unsuccessfully.
Also I've applied different variants with setTimeout and promise for case when paragraph is cleared and only then you run function typeCharacters(rusType). Still not working and no errors in console.
And the same result I get with English.
Looks like when I click select button(not options) it again fires function to type text, not waits till I use options. And when I click option it fires to times simultaneously.
Here is the entire code and weird result:
https://jsfiddle.net/AlexThunders/k8s79zL5/18/
Instead of binding an event handler to the click event, you should only fire the callback when a user changes the selected option of a select element. So the event type should be change:
select.addEventListener('change', () => {
allLangOptions.forEach((option) => {
if (option.selected === true) {
let lang = option.value;
searchLang(lang);
}
});
});
This way you can also use your keyboard to change the options. Although keyboard users could change the language option, as this is not a mouse click, your click event would not fire and the callback inside your addEventListener would not run. Make sure you always hook to the change event of the select element.
Another problem is that you never cancel the interval. When you select a language, it just starts another timer which just messes up everything. It will look like the effect is sped up, the text become gibberish as the different characters are getting mixed, etc.
To avoid this, you need to save the interval ID returned from window.setInterval() and cancel it when you run typeCharacters():
// ...
let reset = false;
// create a variable in an outer scope
let activeInterval = null;
function typeCharacters(phrases) {
// clear running interval
clearInterval(activeInterval);
if (reset) return;
if(phrases[count] !== undefined
&& phrases[count] !== null
&& phrases[count] !== "") {
let allLength = phrases[count].length
// save the interval ID
activeInterval = setInterval(() => {
if(phrases[count] !== undefined && typePar !== null) {
// ... rest of your code
I ran into a roadblock when trying to update a hook when the web socket is called with new information and noticed that the hooks are returning the default values I set them to inside my useEffect, whilst inside the render it is returning the correct values. I am completely stumped and unsure why and was curious as to if anyone could help, much appreciated.
const [view, setView] = useState(false)
const [curFlip, setFlip] = useState(null)
tradeSocket.addEventListener('message', async (msg) => {
const message = JSON.parse(msg.data)
if (message.tradelink) {
// not needed
} else if (message.redItems || message.blueItems) {
// not needed
} else if (message.flips) {
console.log('effect ', view, curFlip) // this is where the issue occurs, it returns false and null
if (view && curFlip) {
console.log('theyre viewing a flip')
for (let i = 0; i < message.flips.length; i++) {
console.log('looping ' + i, message.flips[i].offer)
if (message.flips[i].offer === curFlip.offer) {
setFlip(message.flips[i])
}
}
}
setCoinflips(message.flips)
} else if (message.tradeid) {
// not needed
}
})
Image of what values it returns per render / effect called.
Based on our output, it seems that you set up the socket listener only once on initial render in useEffect.
Now since the useEffect callback is run once, the values used from closure inside the listener function will always show the initial valued
The solution here is to add view and curFlip to dependency array of useEffect and close the socket in useEffect cleanup function
useEffect(() => {
tradeSocket.addEventListener('message', async (msg) => {
const message = JSON.parse(msg.data)
if (message.tradelink) {
// not needed
} else if (message.redItems || message.blueItems) {
// not needed
} else if (message.flips) {
console.log('effect ', view, curFlip) // this is where the issue occurs, it returns false and null
if (view && curFlip) {
console.log('theyre viewing a flip')
for (let i = 0; i < message.flips.length; i++) {
console.log('looping ' + i, message.flips[i].offer)
if (message.flips[i].offer === curFlip.offer) {
setFlip(message.flips[i])
}
}
}
setCoinflips(message.flips)
} else if (message.tradeid) {
// not needed
}
})
return () => {
tradeSocket.close();
}
}
}, [curFlip, view]);
I have a pretty simple method/function that I'm calling in Vue upon button click, which changes state (also toggles text on the button).
pauseTask: function() {
this.isOpen = !this.isOpen;
this.pauseButton.text = this.isOpen ? 'Pause' : 'Resume';
},
It works perfectly, but I need to make an axios call each time based on the state so if this.isOpen then I want to call:
axios.post('/item/status/pause',data)
.then((response) => {
// handle success
console.log(response.data);
if (response.data.success == false) {
this.errors = [];
const errorLog = Object.entries(response.data.errors);
for (var i = errorLog.length - 1; i >= 0; i--) {
console.log(errorLog[i][1][0]);
this.errors.push(errorLog[i][1][0]);
}
}
});
and if !this.isOpen then:
axios.post('/item/status/resume',data)
.then((response) => {
// handle success
console.log(response.data);
if (response.data.success == false) {
this.errors = [];
const errorLog = Object.entries(response.data.errors);
for (var i = errorLog.length - 1; i >= 0; i--) {
console.log(errorLog[i][1][0]);
this.errors.push(errorLog[i][1][0]);
}
}
});
How exactly can I achieve this with the current structure?
Id make a seperate js ( or ts ) file for talking to you API and put those calls into it, then import it as a 'itemService' or something
So from your "pauseTask" you could then do
if(this.isOpen) { itemService.pause(item) } else {itemService.resume(item)}
or you can trigger it on a watch for isOpen
watch: {
isOpen: function (val) {
if(val) { itemService.pause(item) } else {itemService.resume(item)}
}
the advantage of the watch is that anything that manipulates isOpen will trigger the calls.
I'm using jest + enzyme to test my react component "AnimateImage" which contains an image element:
import * as React from 'react';
import { PureComponent } from 'react';
interface Props {
src: string;
}
class AnimateImage extends PureComponent<Props> {
onImgLoad = (e: Event | {target: HTMLImageElement}) => {
console.log("yes!");
};
render() {
return (
<div className="app-image-container">
<img
ref={c => {
if (!c) {
return;
}
c.onerror = function(e){
console.log("error:" , e);
}
if(!c.onload){
c.onload = this.onImgLoad;
if (c && c.complete && c.naturalWidth !== 0) {
this.onImgLoad({
target: c
})
}
}
}}
src={this.props.src}
/>
</div>
);
}
}
export default AnimateImage;
test code:
test("image ", () => {
const component = mount(<AnimateImage src={url_test}/>);
expect(component).toMatchSnapshot();
console.log("end ##################################################################");
})
the expected result:
the image's onload handler is called and I can see the "yes!" printed in the console.
the real result:
the image's onload handler is not called and the image's complete attribute is false.
my jest configuration:
verbose: true,
transform: {
'.(ts|tsx)': 'ts-jest'
},
snapshotSerializers: ['enzyme-to-json/serializer'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
testEnvironment: "jest-environment-jsdom-fourteen",
testEnvironmentOptions: { "resources": 'usable' },
debug step:
I've confirmed that the Canvas is installed successfully and works well in the jsdom.
the jsdom's resource-loader uses "request-promise-native" package to fetch HTTP resource. The "request-promise-native" package's core is "request" package.
in the "request" package, the request.js file declares a class called Request to handle HTTP request.
But I found that the Request.start() function is never called and the defer function is called with the request's status "abort".
by the way, I've put two "console.log()" in the function where the simulated "window" and "document" call "close" function and "console.log('abort')" in the place where the request is handled.
the result shows that the jsdom "window" is closed before the real HTTP request starts outgoing and then, this request's status is set to be "abort".
bogon: yarn test:dom
yarn run v1.10.1
$ jest --config jest.config.js
PASS animate-image.spec.tsx
✓ image (75ms)
console.log xxxxxxxxx/animate-image.spec.tsx:34
end ##################################################################
window close
document close
http://XXXXX.cdn.com
abort
some piece of code in the request.js, may be helpful to understand the problem:
var defer = typeof setImmediate === 'undefined'
? process.nextTick
: setImmediate
defer(function () {
if (self._aborted) {
return
}
var end = function () {
if (self._form) {
if (!self._auth.hasAuth) {
self._form.pipe(self)
} else if (self._auth.hasAuth && self._auth.sentAuth) {
self._form.pipe(self)
}
}
if (self._multipart && self._multipart.chunked) {
self._multipart.body.pipe(self)
}
if (self.body) {
if (isstream(self.body)) {
self.body.pipe(self)
} else {
setContentLength()
if (Array.isArray(self.body)) {
self.body.forEach(function (part) {
self.write(part)
})
} else {
self.write(self.body)
}
self.end()
}
} else if (self.requestBodyStream) {
console.warn('options.requestBodyStream is deprecated, please pass the request object to stream.pipe.')
self.requestBodyStream.pipe(self)
} else if (!self.src) {
if (self._auth.hasAuth && !self._auth.sentAuth) {
self.end()
return
}
if (self.method !== 'GET' && typeof self.method !== 'undefined') {
self.setHeader('content-length', 0)
}
self.end()
}
}
if (self._form && !self.hasHeader('content-length')) {
// Before ending the request, we had to compute the length of the whole form, asyncly
self.setHeader(self._form.getHeaders(), true)
self._form.getLength(function (err, length) {
if (!err && !isNaN(length)) {
self.setHeader('content-length', length)
}
end()
})
} else {
end()
}
self.ntick = true
})
Request.prototype.start = function () {
// start() is called once we are ready to send the outgoing HTTP request.
// this is usually called on the first write(), end() or on nextTick()
var self = this
if (self.timing) {
// All timings will be relative to this request's startTime. In order to do this,
// we need to capture the wall-clock start time (via Date), immediately followed
// by the high-resolution timer (via now()). While these two won't be set
// at the _exact_ same time, they should be close enough to be able to calculate
// high-resolution, monotonically non-decreasing timestamps relative to startTime.
var startTime = new Date().getTime()
var startTimeNow = now()
}
if (self._aborted) {
return
}
self._started = true
self.method = self.method || 'GET'
self.href = self.uri.href
if (self.src && self.src.stat && self.src.stat.size && !self.hasHeader('content-length')) {
self.setHeader('content-length', self.src.stat.size)
}
if (self._aws) {
self.aws(self._aws, true)
}
// We have a method named auth, which is completely different from the http.request
// auth option. If we don't remove it, we're gonna have a bad time.
var reqOptions = copy(self)
delete reqOptions.auth
debug('make request', self.uri.href)
// node v6.8.0 now supports a `timeout` value in `http.request()`, but we
// should delete it for now since we handle timeouts manually for better
// consistency with node versions before v6.8.0
delete reqOptions.timeout
try {
self.req = self.httpModule.request(reqOptions)
} catch (err) {
self.emit('error', err)
return
}
if (self.timing) {
self.startTime = startTime
self.startTimeNow = startTimeNow
// Timing values will all be relative to startTime (by comparing to startTimeNow
// so we have an accurate clock)
self.timings = {}
}
var timeout
if (self.timeout && !self.timeoutTimer) {
if (self.timeout < 0) {
timeout = 0
} else if (typeof self.timeout === 'number' && isFinite(self.timeout)) {
timeout = self.timeout
}
}
self.req.on('response', self.onRequestResponse.bind(self))
self.req.on('error', self.onRequestError.bind(self))
self.req.on('drain', function () {
self.emit('drain')
})
self.req.on('socket', function (socket) {
// `._connecting` was the old property which was made public in node v6.1.0
var isConnecting = socket._connecting || socket.connecting
if (self.timing) {
self.timings.socket = now() - self.startTimeNow
if (isConnecting) {
var onLookupTiming = function () {
self.timings.lookup = now() - self.startTimeNow
}
var onConnectTiming = function () {
self.timings.connect = now() - self.startTimeNow
}
socket.once('lookup', onLookupTiming)
socket.once('connect', onConnectTiming)
// clean up timing event listeners if needed on error
self.req.once('error', function () {
socket.removeListener('lookup', onLookupTiming)
socket.removeListener('connect', onConnectTiming)
})
}
}
var setReqTimeout = function () {
// This timeout sets the amount of time to wait *between* bytes sent
// from the server once connected.
//
// In particular, it's useful for erroring if the server fails to send
// data halfway through streaming a response.
self.req.setTimeout(timeout, function () {
if (self.req) {
self.abort()
var e = new Error('ESOCKETTIMEDOUT')
e.code = 'ESOCKETTIMEDOUT'
e.connect = false
self.emit('error', e)
}
})
}
if (timeout !== undefined) {
// Only start the connection timer if we're actually connecting a new
// socket, otherwise if we're already connected (because this is a
// keep-alive connection) do not bother. This is important since we won't
// get a 'connect' event for an already connected socket.
if (isConnecting) {
var onReqSockConnect = function () {
socket.removeListener('connect', onReqSockConnect)
clearTimeout(self.timeoutTimer)
self.timeoutTimer = null
setReqTimeout()
}
socket.on('connect', onReqSockConnect)
self.req.on('error', function (err) { // eslint-disable-line handle-callback-err
socket.removeListener('connect', onReqSockConnect)
})
// Set a timeout in memory - this block will throw if the server takes more
// than `timeout` to write the HTTP status and headers (corresponding to
// the on('response') event on the client). NB: this measures wall-clock
// time, not the time between bytes sent by the server.
self.timeoutTimer = setTimeout(function () {
socket.removeListener('connect', onReqSockConnect)
self.abort()
var e = new Error('ETIMEDOUT')
e.code = 'ETIMEDOUT'
e.connect = true
self.emit('error', e)
}, timeout)
} else {
// We're already connected
setReqTimeout()
}
}
self.emit('socket', socket)
})
self.emit('request', self.req)
}
I can't get the HTTP request sent to fetch the image source. Thus I can't get the img.onload handler to be called.
anyone could help me to explain this problem?
Finally I didn't find a way to send a request successfully for loading image.
My solution is: mock the HTMLImageElement's prototype in my test code:
Object.defineProperty(HTMLImageElement.prototype, 'naturalWidth', { get: () => 120 });
Object.defineProperty(HTMLImageElement.prototype, 'complete', { get: () => true });
Thus I don't need to get the real image any more and meanwhile I can finish my test case successfully.