Welcome to the Linux Foundation Forum!

Lab 8.2

Is there a solution to this lab?
As far as I understand you cannot create a promise for an async function that has no callback. Without adapting the three functions you cannot complete this assignment (unless I am misreading the "Call the functions in such a way that ​A​ then ​B​ then ​C​ is printed out" and you are allowed to change them to a Promised version)?

If it can be done, a small hint (not the solution) would be appreciated

Comments

  • I can confirm you it can be done without modify the functions, but I'm so bad explaining it without giving you the solution.
    I gonna try:
    You have 3 functions opA, opB, opC. Transform they with the promisify util. Then do your things.
    Hope this help, if you want my solution send me a message.

  • mbaltusmbaltus Posts: 7
    edited November 2020

    Thanks xwarzdev. I finally managed. I had been playing around with promisying these functions, but didn't manage that when using a .then construct. Finally with using an additional async function in which I use three awaits, and then it works.

    Still puzzling why the .then construct is failing (i.e. it shows them in the parallel order of C, B, A)

  • @mbaltus if you'd like to post the .then code I can take a look

  • @mbaltus Agreed. The signature of promisify is a cb fxn. that is 'error-first.' If I add err parameter to the 'op' function, then works just fine.
    However, as per @xwarzdev , this is not compulsory, so I will keep trying as is.

  • I got it to work with then - basically 'nesting' them - so it's kind of back to the downside of nested CBs/CB Hell 🤷🏽‍♂️

  • I am only able to complete the lab without using promisify. Here is my code:

    'use strict'
    const { promisify } = require('util')

    const print = (err, contents) => {
    if (err) console.error(err)
    else console.log(contents)
    }

    const opA = (cb) => {
    return new Promise( (resolve, reject) =>
    setTimeout(() => resolve(cb(null, 'A')), 500)
    );
    }

    const opB = (cb) => {
    return new Promise( (resolve, reject) =>
    setTimeout(() => resolve(cb(null, 'B')), 250)
    );
    }

    const opC = (cb) => {
    return new Promise( (resolve, reject) =>
    setTimeout(() => resolve(cb(null, 'C')), 125)
    );
    }

    async function run () {
    await opA(print)
    await opB(print)
    await opC(print)
    }

    run().catch(console.error)

    I can't figure out how to do with promisify. Can anyone share the code?

  • nayibnayib Posts: 4
    edited November 2020

    @davidmarkclements

    is it allowed to modify the code already written in the file at the beginning of the exercise or should we use it as it is?
    For instance, to promisify the opA, opB and opC functions, we need to modify them to be error-first callback style, like this:

    const opA = (err, cb) => {
      setTimeout(() => {
        cb(null, "A");
      }, 500);
    };
    

    For the purpose of the exercise, is that allowed or we should use the code as it is ?

  • nayibnayib Posts: 4
    edited November 2020

    HINT. Do not click Response if you do not want to check the response out

    If it is allowed to modify the opX functions to make them error-first callback style functions, the following would be a valid response @davidmarkclements ?

    Response>
    "use strict";
    const { promisify } = require("util");
    
    const print = (err, contents) => {
      if (err) console.error(err);
      else console.log(contents);
    };
    
    const opA = (err, cb) => {
      setTimeout(() => {
        cb(null, "A");
      }, 500);
    };
    
    const opB = (err, cb) => {
      setTimeout(() => {
        cb(null, "B");
      }, 250);
    };
    
    const opC = (err, cb) => {
      setTimeout(() => {
        cb(null, "C");
      }, 125);
    };
    
    const promiseOpA = promisify(opA);
    const promiseOpB = promisify(opB);
    const promiseOpC = promisify(opC);
    
    const run = async () => {
      const A = await promiseOpA(print);
      const B = await promiseOpB(print);
      const C = await promiseOpC(print);
      console.log(`${A}\n${B}\n${C}`);
    };
    
    run();
    

  • mbaltusmbaltus Posts: 7
    edited November 2020

    @davidmarkclements said:
    @mbaltus if you'd like to post the .then code I can take a look

    I managed this in the end. My error with the .then chaining, was that I tried to .then the print function as well. As this does not return a promiss, this is not allowed. By handling the print and the call to the next promissified op, I did manage to get it working.

    So, to conclude, without changing opX and using promify, you can solve this one lab.

  • My code, for reference

    'use strict'
    const { promisify } = require('util')
    
    const print = (err, contents) => { 
      if (err) console.error(err)
      else console.log(contents) 
    }
    
    const opA = (cb) => {
      setTimeout(() => {
        cb(null, 'A')
      }, 500)
    }
    
    const opB = (cb) => {
      setTimeout(() => {
        cb(null, 'B')
      }, 250)
    }
    
    const opC = (cb) => {
      setTimeout(() => {
        cb(null, 'C')
      }, 125)
    }
    
    const printA = promisify(opA);
    const printB = promisify(opB);
    const printC = promisify(opC);
    
    printA().then((err, res) => {
      print(err,res)
      printB().then((err, res) => {
        print(err,res)
        printC().then((err, res) => {
          print(err,res)
        })
      })
    })
    
    
    // Alternative with an async caller function
    /*
    async function printer() {
      const resultA = await printA();
      print(null,resultA);
      const resultB = await printB();
      print(null,resultB);
      const resultC = await printC();
      print(null,resultC);
    }
    
    printer()
    */
    
    // Alternative with .then chaining
    //
    
    
  • good job everyone - that's correct. The function passed to promisify should have the signature (err, val) => {}.

  • @mbaltus and @davidmarkclements , my understanding is that when we use a function that takes a callback to create a new function that returns a promise. Instead of passing the original callback into the function we have to resolve the values of the returned promise. The values that will be resolved will be the values that were passed into the callback in the function definiton. Hence we will carryout this intention of passing those values to callback function calls.

    (I am referring to the print function as a callback function even if it isn't being used as one in some instances.)

    I notice that @mbaltus when you used the Promise.prototype.then() you accidentally assigned the resolve parameter as the error paramter. As a result when you pass your resolved values into the callback function you passed the wrong values in.

    Although the error was hidden becuase the print() method conducts the same actions on each of its arguments it simply logs the err or the contents.

    @mbaltus I also noticed that you misused the then() method. then() returns a promise. and so you donot need to write the code so it looks like 'callback hell' the idoiomatic way to write it is shown below in my code.

    @mbaltus I also noticed that you named the promisified functions print, I think that name is difficult to understand becuase the promisfied function return promsies therefore, I changed there names to promiseA promiseB and promiseC.

    I also refactored some of the given code to my liking. I played around a lot.

    .then solution

    'use strict'
    const { promisify } = require('util')d
    
    const print = (err, contents) => { 
      if (err) console.error(err+ ' this is an error');
      else console.log(contents + ' these are the contents');
    }
    
    const opA = (cb) => {
      setTimeout(() => cb(0, 'A'), 500)
    }
    
    const opB = (cb) => {
      setTimeout(() => cb(0, 'B'), 250)
    }
    
    const opC = (cb) => {
      setTimeout(() => cb(0, 'C'), 125)
    }
    
    const promiseA = promisify(opA);
    const promiseB = promisify(opB);
    const promiseC = promisify(opC);
    
    
    promiseA().then(res => {
      print(0, res);
      return promiseB();
    }).then(res => {
      print(0, res);
      return promiseC();
    }).then(res => {
      print(0, res);
    });
    
    
    /* OUTPUT
    A these are the contents
    B these are the contents
    C these are the contents
    */
    

    async solution

    'use strict'
    const { promisify } = require('util')
    
    const print = (err, contents) => { 
      if (err) console.error(err+ ' this is an error');
      else console.log(contents + ' these are the contents');
    }
    
    const opA = (cb) => {
      setTimeout(() => cb(0, 'A'), 500)
    }
    
    const opB = (cb) => {
      setTimeout(() => cb(0, 'B'), 250)
    }
    
    const opC = (cb) => {
      setTimeout(() => cb(0, 'C'), 125)
    }
    
    let promiseA = promisify(opA);
    let promiseB = promisify(opB);
    let promiseC = promisify(opC);
    
    async function printer() {
      print(0, await promiseA());
      print(0, await promiseB());
      print(0, await promiseC());
    }
    
    printer();
    
    /* OUTPUT
    A these are the contents
    B these are the contents
    C these are the contents
    */
    
    
  • @andrewpartrickmiller thanks for pointing out that mistake. I struggled quite a bit with the .then construct and apparently fixed it incorrectly without noticing that (due to same output/result). I do see where the error is, and instead should add a .catch at the end to catch an err and pass that to print(err, null).

  • Great! This is fun, I really enjoy the interactions here.

  • Thank you everybody. I understand the way of both "promise" and "async"+"await".

  • good job everyone @andrewpartrickmiller is closest to correct and is correct for the exercise but don't forget error handling

    when an awaited promise rejects in an async function it behaves like a throw (a promise rejection is an asynchronous exception).

    To modify @andrewpartrickmiller code, wrap a try/catch around the awaits to do the error handling:


    SPOILERS - reveal for code:
    async function printer() {
      try { 
      print(null, await promiseA());
      print(null, await promiseB());
      print(null, await promiseC());
      } catch (err) {
        print(err);
      }
    }
    

    You can either wrap all of them in which case processing stops after the first failure or you can wrap each of them in a try/catch. For the exercise either way is fine. For real scenarios it depends on the situation.

  • One thing I'd like to know is how the callback is making its way into the async functions? We don't implicitly pass the callback "print" in anywhere yet results are obv getting returned to the print function and printed out

    const a = promisify(opA);
    const b = promisify(opB);
    const c = promisify(opC);
    
    async function printSerial() {
      try {
        print(null, await a());
        print(null, await b());
        print(null, await c());
      } catch (e) {
        console.error(e);
      }
    }
    
    

    If you console.log(cb) from within opA - we see that it is an anon func.

    I think @andrewpartrickmiller touched on this a bit but I'm still wrapping my head around it. Maybe @davidmarkclements can help clarify :)

    (Sorry if this is a double post - was having trouble with my first one.)

  • davidmarkclementsdavidmarkclements Posts: 142
    edited December 2020

    @mmayes

    Here's the util.promisify implementation in Node core:

    https://github.com/nodejs/node/blob/32c04491412a40ac6bea1b717a59141244620028/lib/internal/util.js#L277-L324

    If you remove the noise, the custom symbol stuff and property copying, it basically boils down to this:

    function promisify(original) {
      return function fn(...args) {
        return new Promise((resolve, reject) => {
          const callback = (err, value) => {
            if (err) {
              reject(err);
            } else {
              resolve(value);
            }
          }
          original(...args, callback);
        });
      }
    }
    

    See the const there called callback - that's made by promisify and passed to the function you wrap (e.g. opA etc.).
    So when the wrapped function calls it's callback function it calls that function inside the Promise executor function (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise) which in turns calls the Promises resolve function.

    So if we we're to do this by hand with opA it would be:

    const opA = (cb) => { setTimeout(() => cb(null, 'A'), 500) }
    
    const a = () => new Promise((resolve, reject) => {
      opA((err, value) => {
         if (err) {
            reject(err)
          } else {
            resolve(err)
          }
      })
    })
    
    

    Does that clarify it?

  • Yes! Thanks you @davidmarkclements!

  • @nayib said:
    @davidmarkclements

    is it allowed to modify the code already written in the file at the beginning of the exercise or should we use it as it is?
    For instance, to promisify the opA, opB and opC functions, we need to modify them to be error-first callback style, like this:

    const opA = (err, cb) => {
      setTimeout(() => {
        cb(null, "A");
      }, 500);
    };
    

    For the purpose of the exercise, is that allowed or we should use the code as it is ?

    Hi @nayib! that seems incorrect. I also got confused by the documentation at first but it's the callback itself that should be error-first not the function that is being promisified. In this case, the callback inside opA is already error first hence, no changes needed to the function.

    Just mentioning this here just in case someone else gets confused as I did.

  • jhortalejhortale Posts: 7

    Hi everyone,

    I thought that I could have an array of promises and iterate then in an async / await function but I cannot run it. it is missing something:

    'use strict'
    const { promisify } = require('util')
    
    const print = (err, contents) => { 
      if (err) console.error(err)
      else console.log(contents) 
    }
    
    const opA = (cb) => {
      setTimeout(() => {
        cb(null, 'A')
      }, 500)
    }
    
    const opB = (cb) => {
      setTimeout(() => {
        cb(null, 'B')
      }, 250)
    }
    
    const opC = (cb) => {
      setTimeout(() => {
        cb(null, 'C')
      }, 125)
    }
    
    const ops = [
      promisify(opA), 
      promisify(opB),
      promisify(opC)
    ]
    
    async function run(cb) {
      try {
        ops.forEach(op => cb(null, await op()))
      } catch (err) {
        console.error(err)
      }
    }
    
    run(print)
    
  • @jhortale the ops.forEach method accepts a function. You're trying to await in a function that isn't async.

    Use a for of loop instead and you'll be awaiting in the async run function instead

  • Actually, the first thing I thought of for this lab, was to write my own helper which resolved to an array of values, instead of promisifying each function. Like so:

    const prom = (func) => {
      return new Promise((resolve, reject) => {
        func((err, val) => {
          if (err) {
            return reject(err);
          }
          return resolve(val);
        });
      });
    };
    
    Promise.all([prom(opA), prom(opB), prom(opC)]).then((res) => {
      res.forEach(print);
    });
    
  • @adbutterfield this is exactly what the util.promisify function does

Sign In or Register to comment.