0

I need to build an object that show 2 arrays. For it I call the first promise and then I use its result to call the second promise.

I want know if have some best way to resolve this problem.

The problem is described below.

/**
 * DO NOT USE ASYNC/AWAIT
 * Using the below two functions produce the following output
 * {
 * authors: ['bob', 'sally'],
 * titles: ['Greeting the World', 'Hello World!']
 * }
 * */

const getBooks = () => {
    return new Promise((resolve) => {
        resolve([
        {
            bookId: 1,
            author: "bob"
        },
        {
            bookId: 2,
            author: "sally"
        }
        ]);
    });
};
  
const getTitle = (bookId) => {
    return new Promise((resolve, reject) => {
        switch (bookId) {
        case 1:
            resolve({ title: "Greeting the World" });
            break;
        case 2:
            resolve({ title: "Hello World!" });
            break;
        default:
            reject(new Error("404 - book not found"));
        }
    });
};

let authors = {authors: [], titles: []}
getBooks()
    .then(result => {                
        result.map(
            t => {
                authors.authors.push(t.author)
                getTitle(t.bookId)
                    .then(result => {
                        authors.titles.push(result.title)                         
                    })                
            })
            
            
    }).then(_ => console.log(authors))
      

    
 
Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
  • 1
    When an error complains about there being not enough text, please do not just copy and paste the same text again. Try and actually explain the problem in a different way. You aren't doing anything asynchronous here, so it's unclear why you would use Promises at all. That is something that should be explained, rather than repeating the same text. – Heretic Monkey Mar 12 '21 at 02:11

3 Answers3

0

Your code only just so happens to produce the desired result because Promise constructors run synchronously. If this was a true API and the values were not retrieved entirely synchronously, your code would not work:

/**
 * DO NOT USE ASYNC/AWAIT
 * Using the below two functions produce the following output
 * {
 * authors: ['bob', 'sally'],
 * titles: ['Greeting the World', 'Hello World!']
 * }
 * */

const getBooks = () => {
    return new Promise((resolve) => {
        resolve([
        {
            bookId: 1,
            author: "bob"
        },
        {
            bookId: 2,
            author: "sally"
        }
        ]);
    });
};
  
const getTitle = (bookId) => {
    return new Promise((resolve, reject) => {
        switch (bookId) {
        case 1:
            setTimeout(() => { resolve({ title: "Greeting the World" }) });
            break;
        case 2:
            resolve({ title: "Hello World!" });
            break;
        default:
            reject(new Error("404 - book not found"));
        }
    });
};

let authors = {authors: [], titles: []}
getBooks()
    .then(result => {                
        result.map(
            t => {
                authors.authors.push(t.author)
                getTitle(t.bookId)
                    .then(result => {
                        authors.titles.push(result.title)                         
                    })                
            })
            
            
    }).then(_ => console.log(authors))

To fix it, use Promise.all to wait for an array of mapped Promises to resolve to the strings you need:

const getBooks=()=>new Promise(o=>{o([{bookId:1,author:"bob"},{bookId:2,author:"sally"}])}),getTitle=o=>new Promise((e,t)=>{switch(o){case 1:e({title:"Greeting the World"});break;case 2:e({title:"Hello World!"});break;default:t(new Error("404 - book not found"))}});

getBooks()
    .then(result => Promise.all(result.map(
      obj => Promise.all([
        obj, // pass the bookId / author along to the next `.then`
        getTitle(obj.bookId) // retrieve the title for this book
      ])
    )))
    .then((resultsWithTitles) => {
      const authors = resultsWithTitles.map(([obj]) => obj.author);
      const titles = resultsWithTitles.map(([, titleObj]) => titleObj.title);
      console.log({ authors, titles });
    })
    // if this was a true API, you'd want to catch possible errors here
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
0

Using Promise.all to get title of all books:

let authors = { authors: [], titles: [] }
getBooks()
  .then(books => {
    const promises = books.map(book => {
      authors.authors.push(book.author)
      getTitle(book.bookId)
        .then(({ title }) => {
          authors.titles.push(title)
        });
    });
    return Promise.all(promises);
  }).then(_ => console.log(authors))

My recommendation is to use async/await for some task like that. Your code will become like this:

const authors = { authors: [], titles: [] }
const books = await getBooks()
for (const book of books) {
  const { title } = await getTitle(book.bookId)
  authors.authors.push(book.author)
  authors.titles.push(title)
}
console.log(authors);
hoangdv
  • 15,138
  • 4
  • 27
  • 48
0

promise pitfall

See how you define authors before your Promise sequence? This is the most common mistake made by beginners.

Here's one way you can do it by sequencing then calls -

const getAuthors = () =>
  getBooks()
    .then(r => r.map(appendTitle))
    .then(r => Promise.all(r))
    .then(r => ({
      authors: r.map(b => b.author),
      titles: r.map(b => b.title)
    }))

const appendTitle = b =>
  getTitle(b.bookId)
    .then(r => Object.assign(b,r))
getAuthors ()
  .then(console.log, console.error)

{
  "authors": [
    "bob",
    "sally"
  ],
  "titles": [
    "Greeting the World",
    "Hello World!"
  ]
}

Expand the snippet below to verify the results in your own browser -

const getBooks = () => {
    return new Promise((resolve) => {
        resolve([
        {
            bookId: 1,
            author: "bob"
        },
        {
            bookId: 2,
            author: "sally"
        }
        ]);
    });
};
  
const getTitle = (bookId) => {
    return new Promise((resolve, reject) => {
        switch (bookId) {
        case 1:
            resolve({ title: "Greeting the World" });
            break;
        case 2:
            resolve({ title: "Hello World!" });
            break;
        default:
            reject(new Error("404 - book not found"));
        }
    });
};

const appendTitle = b =>
  getTitle(b.bookId)
    .then(r => Object.assign(b,r))

const getAuthors = () =>
  getBooks()
    .then(r => r.map(appendTitle))
    .then(r => Promise.all(r))
    .then(r => ({
      authors: r.map(b => b.author),
      titles: r.map(b => b.title)
    }))
     
getAuthors ().then(console.log, console.error)

improve getBooks and getTitle

Because getBooks and getTitle are simple synchronous functions, I would argue using a Promise with executor function is a misuse of Promises. We could rewrite them to be much simpler -

const getBooks = () =>
  Promise.resolve(BOOKS)
  
const getTitle = (bookId) =>
{ if (bookId in TITLES)
    return Promise.resolve({ title: TITLES[bookId] })
  else
    return Promise.reject(new Error("404 - book not found"))
}

Where BOOKS and TITLES are defined as -

const BOOKS =
  [
    {
      bookId: 1,
      author: "bob"
    },
    {
      bookId: 2,
      author: "sally"
    }
  ]

const TITLES =
  {
    1: "Greeting the World",
    2: "Hello World!"
  }

Expand the snippet below to verify the results in your own browser -

const BOOKS =
  [
    {
      bookId: 1,
      author: "bob"
    },
    {
      bookId: 2,
      author: "sally"
    }
  ]

const TITLES =
  {
    1: "Greeting the World",
    2: "Hello World!"
  }

const getBooks = () =>
  Promise.resolve(BOOKS)
  
const getTitle = (bookId) =>
{ if (bookId in TITLES)
    return Promise.resolve({ title: TITLES[bookId] })
  else
    return Promise.reject(new Error("404 - book not found"))
}
const appendTitle = b =>
  getTitle(b.bookId)
    .then(r => Object.assign(b,r))

const getAuthors = () =>
  getBooks()
    .then(r => r.map(appendTitle))
    .then(r => Promise.all(r))
    .then(r => ({
      authors: r.map(b => b.author),
      titles: r.map(b => b.title)
    }))
     
getAuthors ().then(console.log, console.error)
Mulan
  • 129,518
  • 31
  • 228
  • 259