Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【译】JavaScript中的Promises #19

Open
reng99 opened this issue May 2, 2019 · 0 comments
Open

【译】JavaScript中的Promises #19

reng99 opened this issue May 2, 2019 · 0 comments
Labels
blog a single blog javascript javascript tag translation translate somehing

Comments

@reng99
Copy link
Owner

reng99 commented May 2, 2019

你有没有在JavaScript中遇到过promises并想知道它们是什么?它们为什么会被称为promises呢?它们是否和你以任何方式对另一个人做出的承诺有关呢?

此外,你为什么要使用promises呢?与传统的JavaScript操作回调(callbacks)相比,它们有什么好处呢?

在本文中,你将学习有关JavaScript中promises的所有内容。你将明白它们是什么,怎么去使用它们,以及为什么它们比回调更受欢迎。

所以,promise是什么?

promise是一个将来会返回值的对象。由于这种未来的东西,Promises非常适合异步JavaScript操作。

如果你不明白异步JavaScript意味着什么,你可能还不适合读这篇文章。我建议你回到关于callbacks这篇文章了解后再回来。

通过类比会更好地解析JavaScript promise的概念,所以我们来这样做(类比),使其概念更加清晰。

想象一下,你准备下周为你的侄女举办生日派对。当你谈到派对时,你的朋友,Jeff,提出他可以提供帮助。你很高心,让他买一个黑森林(风格的)生日蛋糕。Jeff说可以。

在这里,Jeff告诉你他会给你买一个黑森林生日蛋糕。这是约定好的。在JavaScript中,promise的工作方式和现实生活中的承诺一样。可以使用以下方式编写JavaScript版本的场景:

// jeffBuysCake is a promise
const promise = jeffBuysCake('black forest')

你将学习如何构建jeffBuysCake。现在,把它当成一个promise

现在,Jeff尚未采取行动。在JavaScript中,我们说承诺(promise)正在等待中(pending)。如果你console.log一个promise对象,就可以验证这点。

pending

打印jeffBuysCake表明承诺正在等待中。

当我们稍后一起构建jeffBuysCake时,你将能够自己证明此console.log语句。

在与Jeff交谈之后,你开始计划下一步。你意识到如果Jeff信守诺言,并在聚会时买来一个黑森林蛋糕,你就可以按照计划继续派对了。

如果Jeff确实买来了蛋糕,在JavaScript中,我们说这个promise是实现(resolved)了。当一个承诺得到实现时,你会在.then调用中做下一件事情:

jeffBuysCake('black forest')
  .then(partyAsPlanned) // Woohoo! 🎉🎉🎉

如果Jeff没给你买来蛋糕,你必须自己去面包店买了。(该死的,Jeff!)。如果发生这种情况,我们会说承诺被拒绝(rejected)了。

当承诺被拒绝了,你可以在.catch调用中执行应急计划。

jeffBuysCake('black forest')
  .then(partyAsPlanned)
  .catch(buyCakeYourself) // Grumble Grumble... #*$%

我的朋友,这就是对Promise的剖析了。

在JavaScript中,我们通常使用promises来获取或修改一条信息。当promise得到解决时,我们会对返回的数据执行某些操作。当promise拒绝时,我们处理错误:

getSomethingWithPromise()
  .then(data => {/* do something with data */})
  .catch(err => {/* handle the error */})

现在,你知道一个promise如何运作了。让我们进一步深入研究如何构建一个promise

构建一个promise

你可以使用new Promise来创建一个promise。这个Promise构造函数是一个包含两个参数 -- resolvereject 的函数。

const promise = new Promise((resolve, reject) => {
  /* Do something here */
})

如果resolve被调用,promise成功并继续进入then链式(操作)。你传递给resolve的参数将是接下来then调用中的参数:

const promise = new Promise((resolve, reject) => {
  // Note: only 1 param allowed
  return resolve(27)
})

// Parameter passed resolve would be the arguments passed into then.
promise.then(number => console.log(number)) // 27

如果reject被调用,promise失败并继续进入catch链式(操作)。同样地,你传递给reject的参数将是catch调用中的参数:

const promise = new Promise((resolve, reject) => {
  // Note: only 1 param allowed
  return reject('💩💩💩')
})

// Parameter passed into reject would be the arguments passed into catch.
promise.catch(err => console.log(err)) // 💩💩💩

你能看出resolvereject都是回调函数吗?😉

让我们练习一下,尝试构建jeffBuysCake promise。

首先,你知道Jeff说他会买一个蛋糕。那就是一个承诺。所以,我们从空promise入手:

const jeffBuysCake = cakeType => {
  return new Promise((resolve, reject) => {
    // Do something here
  })
}

接下来,Jeff说他将在一周内购买蛋糕。让我们使用setTimeout函数模拟这个等待七天的时间。我们将等待一秒,而不是七天:

const jeffBuysCake = cakeType => {
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      // Checks if Jeff buys a black forest cake
    }, 1000)
  })
}

如果Jeff在一秒之后买了个黑森林蛋糕,我们就会返回promise,然后将黑森林蛋糕传递给then

如果Jeff买了另一种类型的蛋糕,我们拒接这个promise,并且说no cake,这会导致promise进入catch调用。

const jeffBuysCake = cakeType => {
  return new Promise((resolve, reject) => {
    setTimeout(()=> {
      if (cakeType
 - = 'black forest') {
        resolve('black forest cake!')
      } else {
        reject('No cake 😢')
      }
    }, 1000)
  })
}

让我们来测试下这个promise。当你在下面的console.log记录时,你会看到promise正在pedding(等待)。(如果你立即检查控制台,状态将只是暂时挂起状态。如果你需要更多时间检查控制台,请随时将超时时间延长至10秒)。

const promise = jeffBuysCake('black forest')
console.log(promise)

pending

打印jeffBuysCake表明承诺正在等待中。

如果你在promise链式中添加thencatch,你会看到black forest cake!no cake 😢信息,这取决于你传入jeffBuysCake的蛋糕类型。

const promise = jeffBuysCake('black forest')
  .then(cake => console.log(cake))
  .catch(nocake => console.log(nocake))

hascake

打印出来是“黑森林蛋糕”还是“没有蛋糕”的信息,取决于你传入jeffBuysCake的(参数)。

创建一个promise不是很难,是吧?😉

既然你知道什么是promise,如何制作一个promise以及如何使用promise。那么,我们来回答下一个问题 -- 在异步JavaScript中为什么要使用promise而不是回调呢?

Promises vs Callbacks

开发人员更喜欢promises而不是callbacks有三个原因:

  1. Promise减少了嵌套代码的数量
  2. Promise允许你轻松地可视化执行流程
  3. Promise让你可以在链式的末尾去处理所有错误

为了看到这三个好处,让我们编写一些JavaScript代码,它们通过callbackspromises来做一些异步事情。

对于这个过程,假设你正在运营一个在线商店。你需要在客户购买东西时向他收费,然后将他们的信息输入到你的数据库中。最后,你将向他们发送电子邮件:

  1. 向客户收费
  2. 将客户信息输入到数据库
  3. 发送电子邮件给客户

让我们一步一步地解决。首先,你需要一种从前端到后端获取信息的方法。通常,你会对这些操作使用post请求。

如果你使用ExpressNode,则初始化代码可能如下所示。如果你不知道任何NodeExpress(的知识点),请不要担心。它们不是本文的主要部分。跟着下面来走:

// A little bit of NodeJS here. This is how you'll get data from the frontend through your API.
app.post('/buy-thing', (req, res) => {
  const customer = req.body

  // Charge customer here
})

让我们先介绍一下基于callback的代码。在这里,你想要向客户收费。如果收费成功,则将其信息添加到数据库中。如果收费失败,则会抛出错误,因此你的服务器可以处理错误。

代码如下所示:

// Callback based code
app.post('/buy-thing', (req, res) => {
  const customer = req.body

  // First operation: charge the customer
  chargeCustomer(customer, (err, charge) => {
    if (err) throw err

    // Add to database here
  })
})

现在,让我们切换到基于promise的代码。同样地,你向客户收费。如果收费成功,则通过调用then将其信息添加到数据库中。如果收费失败,你将在catch调用中自动处理:

// Promised based code
app.post('/buy-thing', (req, res) => {
  const customer = req.body

  // First operation: charge the customer
  chargeCustomer(customer)
    .then(/* Add to database */)
    .catch(err => console.log(err))
})

继续,你可以在收费成功后将你的客户信息添加到数据库中。如果数据库操作成功,则会向客户发送电子邮件。否则,你会抛出一个错误。

考虑到这些步骤,基于callback的代码如下:

// Callback based code
app.post('/buy-thing', (req, res) => {
  const customer = req.body

  chargeCustomer(customer, (err, charge) => {
    if (err) throw err

    // Second operation: Add to database
    addToDatabase(customer, (err, document) => {
      if (err) throw err

      // Send email here
    })
  })
})

对于基于promise的代码,如果数据库操作成功,则在下一个then调用时发送电子邮件。如果数据库操作失败,则会在最终的catch语句中自动处理错误:

// Promised based code
app.post('/buy-thing', (req, res) => {
  const customer = req.body

  chargeCustomer(customer)
    // Second operation: Add to database
    .then(_ => addToDatabase(customer))
    .then(/* Send email */)
    .catch(err => console.log(err))
})

继续最后一步,在数据库操作成功时向客户发送电子邮件。如果成功发送此电子邮件,则会有成功消息通知到你的前端。否则,你抛出一个错误:

以下是基于callback的代码:

app.post('/buy-thing', (req, res) => {
  const customer = req.body

  chargeCustomer(customer, (err, charge) => {
    if (err) throw err

    addToDatabase(customer, (err, document) => {
      if (err) throw err

      sendEmail(customer, (err, result) => {
        if (err) throw err

        // Tells frontend success message.
        res.send('success!')
      })
    })
  })
})

然后,以下基于promise的代码:

app.post('/buy-thing', (req, res) => {
  const customer = req.body

  chargeCustomer(customer)
    .then(_ => addToDatabase(customer))
    .then(_ => sendEmail(customer) )
    .then(result => res.send('success!')))
    .catch(err => console.log(err))
})

看看为什么使用promises而不是callbacks编写异步代码要容易得多?你从回调地狱(callback hell)一下子切换到了链式乐土上😂。

一次触发多个promises

promisescallbacks的另一个好处是,如果操作不依赖于彼此,则可以同时触发两个(或多个)promises,但是执行第三个操作需要两个结果。

为此,你使用Promise.all方法,然后传入一组你想要等待的promises。then的参数将会是一个数组,其包含你promises返回的结果。

const friesPromise = getFries()
const burgerPromise = getBurger()
const drinksPromise = getDrinks()

const eatMeal = Promise.all([
  friesPromise,
  burgerPromise,
  drinksPromise
])
  .then([fries, burger, drinks] => {
    console.log(`Chomp. Awesome ${burger}! 🍔`)
    console.log(`Chomp. Delicious ${fries}! 🍟`)
    console.log(`Slurp. Ugh, shitty drink ${drink} 🤢 `)
  })

备注:还有一个名为Promise.race的方法,但我还没找到合适的用例。你可以点击这里去查看。

最后,我们来谈谈浏览器支持情况!如果你不能在生产环境中使用它,那为什么要学习promises呢。是吧?

浏览器支持Promise

令人兴奋的消息是:所有主流浏览器都支持promises

如果你需要支持IE 11及其以下版本,你可以使用Taylor Hakes制作的Promise Polyfill。它支持IE8的promises。😮

结语

你在本文中学到了所有关于promises的知识。简而言之,promises棒极了。它可以帮助你编写异步代码,而无需进入回调地狱。

尽管你可能希望无论什么时候都使用promises,但有些情况callbacks也是有意义的。不要忘记了callbacks啊😉。

如果你有疑问,请随时在下面发表评论,我会尽快回复你的。【PS:本文译文,若需作者解答疑问,请移步原作者文章下评论】

感谢阅读。这篇文章是否帮助到你?如果有,我希望你考虑分享它。你可能会帮助到其他人。非常感谢!

后话

原文:https://zellwk.com/blog/js-promises/

下一篇关于 async/await

@reng99 reng99 added blog a single blog translation translate somehing javascript javascript tag labels May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blog a single blog javascript javascript tag translation translate somehing
Projects
None yet
Development

No branches or pull requests

1 participant