0

I'm working with asynchronous javascript in NodeJS. I have a function, that modifies it's parameters and then resolve and emits to SocketIO client. The problem is, the function doesn't process the lines in order, it makes some process first and some process after it, I think it is just about asynchronous JavaScript problem, but I can't understand what to do for solve this.

My function,

const processStylesAndImages = (target, targetSlug, id, socket, styles, images, protocol, CSS, DOM) => {
    return new Promise(async (resolve, reject) => {
        let { coverage } = await CSS.takeCoverageDelta();
        let { nodes } = await DOM.getFlattenedDocument({ depth: -1 });
        styles = styles.filter(style => !style.isInline && style.sourceURL.trim().length > 0);
        for(let style of styles) {
            style.mediaQueries = [];
            let m;
            while(m = mediaQueryRegex.exec(style.css)) {
                style.mediaQueries.push({
                    startOffset: m.index,
                    endOffset: m.index + m[0].length,
                    rule: style.css.slice(m.index, m.index + m[0].length),
                    used: true,
                    rules: []
                });
            }
            style.used = [];
            while(m = charsetRegex.exec(style.css)) {
                style.used.push({
                    startOffset: m.index,
                    endOffset: m.index + m[0].length,
                    used: true,
                    styleSheetId: style.styleSheetId
                });
            }
            while(m = importRegexVariable.exec(style.css)) {
                style.used.push({
                    startOffset: m.index,
                    endOffset: m.index + m[0].length,
                    used: true,
                    styleSheetId: style.styleSheetId
                });
            }
            let fontFaces = [];
            while(m = fontFaceRegex.exec(style.css)) {
                fontFaces.push(m);
            }
            fontFaces.forEach((m, index) => {
                let pushed = false;
                let props = css.parse(style.css.slice(m.index, m.index + m[0].length)).stylesheet.rules[0].declarations;
                let fontFamily;
                let fontWeight = null;
                props.forEach(prop => {
                    if(prop.property == 'font-family') {
                        if(prop.value.startsWith("'") || prop.value.startsWith('"')) {
                            prop.value = prop.value.slice(1);
                        }
                        if(prop.value.endsWith("'") || prop.value.endsWith('"')) {
                            prop.value = prop.value.slice(0, -1);
                        }
                        prop.value = prop.value.toLowerCase();
                        fontFamily = prop.value;
                    } else if(prop.property == 'font-weight') {
                        fontWeight = prop.value;
                    }
                });
                if(fontWeight == null || 'normal') fontWeight = 400;
                if(style.sourceURL == 'https://www.webmedya.com.tr/css/font-awesome.min.css') console.log(fontFamily, fontWeight);
                nodes.forEach(async (node, nodeIndex) => {
                    let { computedStyle } = await CSS.getComputedStyleForNode({ nodeId: node.nodeId });
                    computedStyle = computedStyle.filter(item => {
                        return (item.name == 'font-family' || item.name == 'font-weight') && (item.value !== '' || typeof(item.value) !== 'undefined');
                    });
                    let elementFontFamily;
                    let elementFontWeight;
                    computedStyle.forEach(compute => {
                        if(compute.name == 'font-family' && compute.value !== '' && typeof(compute.value) !== 'undefined') {
                            elementFontFamily = compute.value.toLowerCase();
                        } else if(compute.name == 'font-weight') {
                            if(compute.value !== '' && typeof(compute.value) !== 'undefined') {
                                if(compute.value == 'normal') {
                                    elementFontWeight = 400;
                                } else {
                                    elementFontWeight = compute.value;
                                }
                            } else {
                                elementFontWeight = 400;
                            }
                        }
                    });
                    if(elementFontFamily && elementFontWeight) {
                        if(elementFontFamily.includes(fontFamily) && (elementFontWeight == fontWeight)) {
                            if(!pushed) {
                                //console.log(m);
                                style.used.push({
                                    startOffset: m.index,
                                    endOffset: m.index + m[0].length,
                                    used: true,
                                    styleSheetId: style.styleSheetId
                                });
                                pushed = true;
                                console.log('Pushed', style.css.slice(m.index, m.index + m[0].length));
                            }
                        }
                    }
                });
            });
            console.log('BBBBBBBBBBBBB');
            console.log('AAAAAAAAAAAA');
            let parsedSourceURL = url.parse(style.sourceURL.trim());
            if(parsedSourceURL.protocol === null && parsedSourceURL.host === null) {
                if(style.sourceURL.trim().startsWith('/')) {
                    style.sourceURL = `${target}${style.sourceURL.trim()}`;
                } else {
                    style.sourceURL = `${target}/${style.sourceURL.trim()}`;
                }
            };
            style.parentCSS = "-1";
            style.childCSSs = [];
            style.childCSSs = getImports(style.css, style.sourceURL.trim(), target);
            coverage.forEach(item => {
                if(item.styleSheetId.trim() == style.styleSheetId.trim())
                    style.used.push(item);
            });
            style.mediaQueries.forEach((mediaQuery, index) => {
                style.used.forEach((usedRule, usedIndex) => {
                    if(usedRule.startOffset > mediaQuery.startOffset && usedRule.endOffset < mediaQuery.endOffset) {
                        style.mediaQueries[index].rules.push(style.used[usedIndex]);
                        style.used[usedIndex] = false;
                    }
                });
            });
            style.used = style.used.filter(item => {
                return item !== false;
            });
            style.mediaQueries = style.mediaQueries.filter(item => {
                return item.rules.length > 0;
            });
            style.mediaQueries.forEach((mediaQuery, index) => {
                mediaQuery.rules.sort((a, b) => a.startOffset - b.startOffset);
            });
            style.used = style.used.concat(style.mediaQueries);
            delete style.mediaQueries;
            style.used.sort((a, b) => a.startOffset - b.startOffset);
            let compressedCss = "";
            if(style.used.length > 0) {
                style.used.forEach(usedRule => {
                    if(usedRule.rule && usedRule.rules.length > 0) {
                        let queryRule = usedRule.rule.match(/@media[^{]+/)[0];
                        compressedCss += queryRule + '{';
                        usedRule.rules.forEach(item => {
                            compressedCss += style.css.slice(item.startOffset, item.endOffset);
                        });
                        compressedCss += '}';
                    } else {
                        compressedCss += style.css.slice(usedRule.startOffset, usedRule.endOffset);
                    }
                });
            };
            style.compressedCss = compressedCss;
        }
        console.log('CCCCCCCCCCCCCCCCCCCC');
        styles = preTraverse(styles, targetSlug, id);
        debug('CSS Dosyaları İşlendi!');
        fs.readFile(`./data/${targetSlug}/${id}/cimg/statistics.json`, async (err, data) => {
            if(err) reject(err);
            try {
                data = JSON.parse(data);
                await CSS.stopRuleUsageTracking();
                await protocol.close();
                if(typeof(styles) !== 'undefined' && styles.length > 0) {
                    debug('IMG Dosyaları İşlendi!');
                    socket.emit('log', { stage: 6, images, data, styles });
                    resolve({ images, data, styles });
                } else {
                    debug('IMG Dosyaları İşlendi!');
                    socket.emit('log', { stage: 6, images, data, styles: [] });
                    resolve({ images, data, styles: [] });
                };
            } catch(e) {
                reject(e);
            };
        });
    });
};

Results when the functions starts for some parameters,

BBBBBBBBBBBBB
AAAAAAAAAAAA
fontawesome 400
BBBBBBBBBBBBB
AAAAAAAAAAAA
BBBBBBBBBBBBB
AAAAAAAAAAAA
CCCCCCCCCCCCCCCCCCCC
Pushed @font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}
Pushed @font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');unicode-range:U+0460-052F,U+20B4,U+2DE0-2DFF,U+A640-A69F}
Pushed @font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}

The expected result is,

BBBBBBBBBBBBB
AAAAAAAAAAAA
fontawesome 400
Pushed @font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}
BBBBBBBBBBBBB
AAAAAAAAAAAA
Pushed @font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTa-j2U0lmluP9RWlSytm3ho.woff2) format('woff2');unicode-range:U+0460-052F,U+20B4,U+2DE0-2DFF,U+A640-A69F}
Pushed @font-face{font-family:open sans;font-style:normal;font-weight:300;src:local('Open Sans Light'),local('OpenSans-Light'),url(https://fonts.gstatic.com/s/opensans/v15/DXI1ORHCpsQm3Vp6mXoaTZX5f-9o1vgP2EXwfjgl7AY.woff2) format('woff2');unicode-range:U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
BBBBBBBBBBBBB
AAAAAAAAAAAA
CCCCCCCCCCCCCCCCCCCC

The function is skips the for loop at line 6 in JSFiddle. It behaves like asynchronous process, but I want to it behave like synchronous.

Thanks!

  • Alright @CertainPerformance, thanks! – Muhammed Çağlar TUFAN Dec 31 '18 at 11:29
  • A couple of anti-patterns here. -> `return new Promise(async (resolve, reject)` and `nodes.forEach(async (node, nodeIndex)` forEach has no notion of promises, unless you push into an array and `Promise.all` them. – Keith Dec 31 '18 at 12:03
  • Yes, @Keith, you are right. The problem is by `array.forEach(async ...)`, now my code is working but what is the proof of this? – Muhammed Çağlar TUFAN Dec 31 '18 at 12:51
  • Not sure what you mean by proof, foreach is just a synchrouse looping construct, more info here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach – Keith Dec 31 '18 at 13:06
  • Alright! Thanks for your time! I'm so glad! Have a good day sir! – Muhammed Çağlar TUFAN Dec 31 '18 at 13:09
  • [Never pass an `async function` as the executor to `new Promise`!](https://stackoverflow.com/q/43036229/1048572) You should put the `new Promise` wrapper at most around the `fs.readFile()` call, and then only call `resolve`/`reject` in the callback but do nothing else. Or just use `fs.promise` right away. – Bergi Dec 31 '18 at 14:39

1 Answers1

1

You should await the new Promise((res, rej) => { promise on line 39 in your fiddle. You create a promise with .then() and .catch() handlers which you run it within your loop, but don't await it. Meaning, the promise is triggered, but the code continues to the next iteration already.

So try to add await in front of that new Promise(...) on line 39 and run it.

Segers-Ian
  • 1,027
  • 10
  • 21
  • I updated the function code. I did tried your advice but, it didn't work :/ Can you check new code, please? – Muhammed Çağlar TUFAN Dec 31 '18 at 11:55
  • I can't see the new `await new Promise()` but I did notice also you have `nodes.forEach(async (node, nodeIndex)`. `.forEach` does not handle `async` perse in a correct sequence. Try to replace any `foreach(async ...)` occurrence with a normal `for` loop. Let me know if that helps. – Segers-Ian Dec 31 '18 at 12:01
  • Damn son! Thanks buddy, it works fine but what is the real proof of this `array.foreach(async ...)`to `for(item of array)` ? – Muhammed Çağlar TUFAN Dec 31 '18 at 12:48
  • What do you mean with "what is the real proof " ? So I can elaborate :) – Segers-Ian Dec 31 '18 at 13:25
  • So, why the asynchronous functions don't work on `array.forEach()` loop @Ian Segers ? – Muhammed Çağlar TUFAN Dec 31 '18 at 14:15
  • @MuhammedÇağlarTUFAN : So you can run an `async` function in `forEach()` but the `forEach()` function does not wait to the completion of a promise or async function that you provided. The `.forEach()` was originally created to run synchronous functions. So what happens now is that, when you have an async function for `.forEach` the async function is "triggered" but then the `forEach()` will already continue to the next item of the array. As you have multiple functions triggered after each other without waiting on the previous, its hard to guess the order they complete in. Hope that helps :) – Segers-Ian Jan 01 '19 at 14:40