新的Promise()与(async () => {})()的区别

5

块1:

const promise = new Promise((resolve) => {
  setTimeout(resolve, 100);
});

区块 2:

const promise = (async () => {
  await new Promise(resolve => {
    setTimeout(resolve, 100);
  });
})();

上述两个块是否等效?有值得注意的区别吗?
我知道这是一个高度人为的例子,在这里 Block 2 没有太多用途。但我遇到的情况是,我想创建并存储对 Promise 的引用,但 Promise 执行程序函数需要使用 await 获取一些数据。但是声明 new Promise(async (resolve) => {}); 被认为是一种反模式。在这种情况下,Block 2 是否更好?
更新:提供一个更具体的示例,说明我要做什么:
  export async function getData(request) {
    // De-dupe fetches for the same request and return existing promise.
    if (map.has(JSON.stringify(request))) {
      return map.get(JSON.stringify(request));
    }

    const thePromise = (async () => {
      const [foo, bar] = Promise.all(await getFoo(), await getBar());

      const theData = await getTheData(foo, bar);

      return theData.some.thing ? 'a' : 'b';
    })();

    map.put(JSON.stringify(request), thePromise);
    return thePromise;
  }

使用 async 关键字会有一些额外开销,但如果你正在使用 babel,你可以看到它将根据 ES 版本生成什么。我几乎可以确定 ES6 浏览器做了类似的事情,但说实话并不太确定。 - Filip Cordas
承诺执行函数需要使用await获取一些数据” - 不需要。你说得对,将“async函数”作为执行器传递给“new Promise”是一个反模式。您能否展示一个具体案例的实际代码? - Bergi
我的执行器需要进行两个异步调用以获取一些数据,然后再进行另一个异步调用并根据第三个调用的结果进行解决。我可以使用 Promise 链接而不是 Async/await,但我不明白为什么我必须混合两种模式。 - kufudo
添加了一个更具体的例子。 - kufudo
@kufudo 谢谢。那个例子很好(除了 Promise.all(await getFoo(), await getBar());),而且它没有在任何地方使用 new Promise 构造函数和执行程序。 - Bergi
3个回答

1
在第二种方法中,您可以使用trycatch块,如下所示:
const promise = (async () => {
  try {
    await new Promise(resolve => {
      setTimeout(resolve, 100);
    });
  } catch(err) {
    // handle error here
  }
})();

0

不确定你所说的是不是这个:

但是 Promise 执行函数需要使用 await 来获取一些数据

但是听起来你正在处理一个需要“等待”的 Promise 内部的某些异步调用,然后用返回的数据作为结果进行 resolve

例如:

编辑:如下方评论中@Bergi指出,您可能不应该这样做(在另一个 Promise 中包装一个 Promise):

// Assuming you're making an API with `axios` to grab some data
const promise = new Promise((resolve, reject) => {
  axios.get('www.example.com/api', {...})
    .then(result => {
      resolve(result)
    })
    .catch(error => {
      reject(error)
    })
})
const callback = (data) => {
  console.log('data', data)
}

// `callback` will get called when the `axios.get` "finishes" (resolves) 
// with some data after it's done fetching, no need to `await` here

promise.then(callback)

如果需要的话,您可以使用Promise链式调用:

// using the same example from above
const handleResponse = (result) => {
  // handle response
}
const handleError = (error) => {
  // handle error
}

axios.get('www.example.com/api', {...})
  .then(handleResponse)
  .then(doSomethingElse)
  .catch(handleError)

// or, if you need to make multiple asynchronous calls use `Promise.all`
const handleResponses = (response) => {
  const [users, books] = response
  // do the rest
}

Promise.all([
  axios.get('www.example.com/api/users'),
  axios.get('www.example.com/api/books')
])
  .then(handleAPIResponses)
  .then(doSomethingElse)
  .catch(handleError)

同样地,如果你正在处理“错误优先”的回调模式。
// `fs.readFile` with Node.js
const promise = new Promise((resolve, reject) => {
  fs.readFile('...', (err, data) => {
    if (err) {
      reject(err)
      return
    }
    resolve(data)
  })
})
const callback = (data) => {
  console.log('data', data)
}

// again, `callback` will get called when reading a file is done
// and you get the result back

promise.then(callback)

如果您需要在一个 promise 内部进行多次调用,然后使用最终值解决,您可以尝试以下操作:
async function promise() {
  try {
    // assuming API.getUserIds() returns a promise
    const userIds = await API.getUserIds(...)
    const users = await API.getUsers(userIds)

    return users
  } catch (err) {
    // ...
  }
}
const callback = (data) => {
  console.log('data', data)
}

// again, `callback` will get called when both API requests 
// resolve with some values and when the above function returns `users`

promise().then(callback)

就我个人而言,我会避免使用#2,不管你试图用那个模式实现什么(取决于你的情况),都可以轻松地从上面的示例中选择一个来完成。


你提供的第一个例子是错误的,在这个例子中,如果你运行它,你会发现你在回调函数中得到了一个promise,你的promise执行函数不会等待axios.get()解析,它只会返回那个挂起的Promise,这就是传递给你的then回调的内容。 - Prithwee Das
你说得对,我已经修复了,谢谢。此外,你的示例(以及我的旧示例)实际上并没有返回任何内容,因此 callback 永远不会被调用,因为我从未在原始 Promise 中执行 resolvereject。所以在你的示例中,promise.then(callback) // Promise {<pending>} 不是一个挂起的 Promise。 - goto
@goto1 你是故意使用Promise构造函数反模式来展示一个不好的实践吗?我无法确定。 - Bergi
@Bergi OP 询问了在解决外部承诺之前等待异步调用完成的方法,所以我展示了不同的实现方式。你说得对,在第一个例子中,将一个承诺包装到另一个承诺中是没有意义的。我提供了一个更好的例子来避免这种情况。谢谢。 - goto

0
提供一个更具体的例子,说明我正在尝试做什么。
你在这里使用async立即执行函数表达式是完全没问题的,没有任何问题。当然,你也可以用.then()链来编写相同的代码,但那不会像现在这样舒适。
一些小的改进:
export function getData(request) {
//    ^ no need for `async` when you return a promise anyway
  const requestKey = JSON.stringify(request); // compute only once
  if (map.has(requestKey)) {
    return map.get(requestKey);
  }
  const thePromise = (async () => {
    const [foo, bar] = await Promise.all([getFoo(), getBar()]);
//                     ^^^^^ had `await` keywords in the wrong places
    const theData = await getTheData(foo, bar);
    return theData.some.thing ? 'a' : 'b';
  })();
  map.set(requestKey, thePromise);
  return thePromise;
}

Promise.all 接受一个可迭代对象作为参数,因此 getFoogetBar 应该被放入一个数组中,像这样:Promise.all([getFoo(), getBar()])... 但我仍然困惑于为什么 async 调用被包装在一个自执行函数中。难道 OP 希望在将 Promise 存储在映射对象之前就解决它吗?如果是这样的话,它是行不通的。 - goto
@goto1 不,楼主希望能够得到一个 Promise 存储在 Map 中(在解决之前立即),并使用 async/await 语法。没有其他方法可以做到这一点,除非使用 IIFE。 - Bergi
你可以直接这样写 const thePromise = async () => {...},然后 map.put('...', thePromise)。当你需要使用该 Promise 时,只需这样写 const myPromise = getData('...') 然后 myPromise().then(...) 即可。不需要使用 IIFE,因为在我看来,这会使代码更加混乱和难以理解,就像这个例子中的情况 - https://jsbin.com/gelopibago/edit?js,console - goto
命名可能会改变,但使用 iffy 一定会使其不太易读... 不确定存储承诺而不是承诺解决的实际值有什么意义... 如果你不够小心,你可能会在从映射中检索某些内容时每次都发出请求,而不是“缓存”请求,这将打败目的。 - goto
@goto1 非常感谢,我只是从原始代码中复制过来,没有注意到这个问题。put 是 Java 中使用的函数名... - Bergi
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接