没有异步/等待的承诺有人可以告诉我解决这个问题的另一种方法吗?

问题描述

我需要构建一个显示 2 个数组的对象。对于它,我调用一个承诺,然后我使用它的结果来调用第二个承诺。

我想知道是否有解决此问题的最佳方法

问题描述如下。

/**
 * 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))
      

    
 

解决方法

您的代码只是碰巧才能产生所需的结果,因为 Promise 构造函数是同步运行的。如果这是一个真正的 API 并且值不是完全同步检索的,那么您的代码将无法运行:

/**
 * 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))

要修复它,请使用 Promise.all 等待映射的 Promise 数组解析为您需要的字符串:

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

,

使用 Promise.all 获取所有图书的 title

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))

我的建议是将 async/await 用于此类任务。你的代码会变成这样:

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);
,

承诺陷阱

看到您如何定义 authors before Promise 序列?这是初学者制作的most common mistake

这是通过对 then 调用进行排序来实现的一种方法 -

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!"
  ]
}

展开下面的代码片段以在您自己的浏览器中验证结果 -

const getBooks = () => {
    return new Promise((resolve) => {
        resolve([
        {
            bookId: 1,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)


改进 getBooks 和 getTitle

因为 getBooksgetTitle 是简单的同步函数,我认为将 Promise 与 executor 函数一起使用是对 Promise 的滥用。我们可以将它们重写得更简单 -

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"))
}

其中 BOOKSTITLES 定义为 -

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

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

展开下面的代码片段以在您自己的浏览器中验证结果 -

const BOOKS =
  [
    {
      bookId: 1,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,console.error)