【译】JavaScript中的Promises

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

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

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

因此,promise是什么?

promise是一个未来会返回值的对象。因为这种将来的东西,Promises很是适合异步JavaScript操做。git

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

经过类比会更好地解析JavaScript promise的概念,因此咱们来这样作(类比),使其概念更加清晰。github

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

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

// jeffBuysCake is a promise
const promise = jeffBuysCake('black forest')
复制代码

你将学习如何构建jeffBuysCake。如今,把它当成一个promise数组

如今,Jeff还没有采起行动。在JavaScript中,咱们说承诺(promise)正在等待中(pending)。若是你console.log一个promise对象,就能够验证这点。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:本文译文,若需做者解答疑问,请移步原做者文章下评论】

感谢阅读。这篇文章是否帮助到你?若是有,我但愿你考虑分享它。你可能会帮助到其余人。很是感谢!

后话

原文:zellwk.com/blog/js-pro…

文章首发:github.com/reng99/blog…

更多内容:github.com/reng99/blog…

下一篇关于 async/await

本文同步分享在 博客“Jimmy”(JueJin)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索