你想了解的Promise,都在这里!!

前言

在JavaScript语言中,代码都是是单线程执行的,正是因为这个缘由,致使了JavaScript中全部的网络操做,浏览器事件,都必须知足异步执行的要求。因此异步的各类方案开始出现并逐步合理化,简单话!api

异步处理

在开发过程当中你们使用的异步处理方案通常包括:回调函数(Callback)PromiseGenerator函数、async/await。这里就主要说一下这些方案的异同:数组

回调函数(Callback)

假设咱们定义一个getData函数用于数据请求:promise

function getData(url, callback) {
  // 模拟数据请求
  setTimeout(() => {
    let res = {
      url: url,
      data: {}
    }
    callback(res)
  }, 1000)
}
复制代码

如今的需求是咱们须要依次请求三次服务器,而且每次请求的数据必须在上次成功的基础上执行:浏览器

getData('/api/page/1?params=123',(res1) => {
  console.log(res1);
  getData(`/api/page/2?params=${res1.data.params}`, (res2) => {
    console.log(res2);
    getData(`/api/page/3?params=${res2.data.params}`, (res3) => {
      console.log(res3);
    })
  })
})
复制代码

经过上面的🌰,咱们能够看到第一次的url:/api/page/1?params=123,第二次的url: /api/page/2?params=${res1.data.params},依赖第一次请求的数据,第三次的url:/api/page/2?params=${res2.data.params},依赖第二次请求的数据。因为咱们每次的数据请求都依赖上次的请求,因此咱们将会将下一次的数据请求以回调函数的形式写在函数内部,这其实就是咱们常说的回掉地狱服务器

Promise

一样的需求,咱们使用Promise,去实现看看:网络

首先咱们须要先将咱们的getData函数改写成Promise的形式异步

function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }
复制代码

那么逻辑代码应该变成:async

getDataPromise('/api/page/1?params=123')
    .then(res1 => {
      console.log(res1);
      return getDataPromise(`/api/page/2?params=${res1.data.params}`)
    })
    .then(res2 => {
      console.log(res2);
      return getDataPromise(`/api/page/3?params=${res2.data.params}`)
    })
    .then(res3 => {
      console.log(res3);
    })
复制代码

这样写完来看,发现咱们每次在数据请求成功(then)以后返回一个Promise对象,方便下次使用,这样咱们就避免了回掉地狱的出现,可是这样其实也不算事完美,当咱们的请求变得复杂的时候咱们会发现咱们的代码会变的更加复杂。函数

为了不这种状况的出现 async/await应运而生。ui

async/await

getData函数不变,仍是Promise

function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }
复制代码

需求代码变成:

async function getData () {
    let res1 = await getDataPromise('/api/page/1?params=123');
    console.log(res1);
    let res2 = await getDataPromise(`/api/page/2?params=${res1.data.params}`);
    console.log(res2);
    let res3 = await getDataPromise(`/api/page/2?params=${res2.data.params}`);
    console.log(res3);
  }

复制代码

怎么样,是否是这段代码阅读起来很是舒服,其实async/await都是基于Promise的,使用async方法最后返回的仍是一个Promise;实际上async/await能够看做是Generator异步处理的语法糖,👇咱们就来看一下使用Generator怎么实现这段代码

Generator

// 异步函数依旧是Promise
  function getDataPromise(url) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        let res = {
          url: url,
          data: {}
        }
        resolve(res)
      }, 1000)
    })
  }

  function * getData() {
    let res1 = yield getDataPromise('/api/page/1?params=123');
    console.log(res1);
    let res2 = yield getDataPromise(`/api/page/2?params=${res1.data.params}`);
    console.log(res2);
    let res3 = yield getDataPromise(`/api/page/2?params=${res2.data.params}`);
    console.log(res3);
  }

复制代码

其实能够分开来看:

let fn = getData()
  fn.next().value.then(res1 => {
    fn.next(res1).value.then(res2 => {
      fn.next(res2).value.then( () => {
        fn.next()
      })
    })
  })

复制代码

上面的代码咱们能够看到,next()每一步之行.value方法返回的都是一个Promise,因此咱们能够在后面添加then方法,在then方法后面我继续调用next(),知道函数运行完成。实际上上面的代码咱们不须要手动去写,咱们能够对其封装一下:

function run(gen) {
    let fn = gen()

    function next(data) {
      let res = fn.next(data)
      if (res.done) return res.value
      res.value.then((info) => {
        next(info)
      })
    }
    next()
  }

  run(getData)
复制代码

run方法用来自动执行一步操做,其实就能够看做是Generator在进行递归操做;

这样咱们就将异步操做封装到了函数内部,其实不难发现async/awaitGenerator有不少类似的地方,只不过async/await在语义上更容易被理解。

在使用async/await的时候咱们不须要在去定义run(),它内部已经给咱们定义封装好了,这也是为何说async/awaitGenerator异步处理的语法糖了。

Promise

上面咱们介绍了回调函数(Callback)PromiseGenerator函数、async/await的区别,下面咱们就来具体说说Promise

Promise.prototype.then()

  • 做用

thenPromise.prototype.catch() 方法都会返回 promise,它们能够被链式调用 — 一种称为复合composition 的操做.

  • 参数

第一个参数:状态从 pending -> fulfilled 时的回调函数

第二个参数:状态从 pending -> rejected 时的回调函数

  • 返回值:新的 Promise 实例(注意不是原来的 Promise 实例

  • 特色

因为 then 方法返回一个新的 Promise 实例,因此 then 方法是能够链式调用的,链式调用的 then 方法有两个特色:

第一:后一个 then 方法的回调函数的参数是前一个 then 方法的返回值

第二:若是前一个 then 方法的返回值是一个 Promise 实例,那么后一个 then 方法的回调函数会等待该 Promise 实例的状态改变后再执行

Promise.prototype.catch

  • 描述

catch 方法能够用于您的promise组合中的错误处理。

Internally calls Promise.prototype.then on the object upon which is called, passing the parameters undefined and the onRejected handler received; then returns the value of that call (which is a Promise).

你们能够看一下下面的代码:

const promise = new Promise(function (resolve, reject) {
    setTimeout(() => {
        reject('err')
    }, 1000)
})

promise.then(
    res => console.log('s1'),
    err => console.log('e1')
).then(
    res => console.log('s2')
).catch(
    err => console.log('e2')
)
复制代码
e1
s2
复制代码

能够发现,在第一个 then 方法执行的错误处理函数中捕获到了错误,因此输出了 e1,那么这个错误已经被捕获到了,也就不须要 catch 再次捕获了,因此没有输出 e2,这是正常的,但问题是居然输出了 s2。。。。 因此为了不这种状况代码应该改成:

promise.then(
    res => console.log('s1')
).then(
    res => console.log('s2')
).catch(
    err => console.log('e2')
)
复制代码

这样只会输出e2

Promise.prototype.finally

当咱们想在Promise不管成功仍是失败的时候都想进行某一步操做时,能够说使用finally

promise.then(
    res => console.log('s1')
).catch(
    err => console.log('e1')
).finally(
    () => console.log('end')
)
复制代码

很容易可以发现,.finally 只不过是一个成功与失败的回调函数相同的 .then 而已。

Promise.all(iterable);

  • 参数(iterable) 一个可迭代的对象,如 Array 或 String;

  • 返回值

    • 若是传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise。
    • 若是传入的参数不包含任何 promise,则返回一个异步完成(asynchronously resolved) Promise。注意:Google Chrome 58 在这种状况下返回一个已完成(already resolved)状态的 Promise。
    • 其它状况下返回一个处理中(pending)的Promise。这个返回的 promise 以后会在全部的 promise 都完成或有一个 promise 失败时异步地变为完成或失败。 见下方关于“Promise.all 的异步或同步”示例。返回值将会按照参数内的 promise 顺序排列,而不是由调用 promise 的完成顺序决定。

🌰:

const p = Promise.all([promise1, promise2, promise3])

  p.then(
      (res) => {
          // res 是结果数组
      }
  )
复制代码

只有当全部 Promise 实例的状态都变为 fulfilled,那么 Promise.all 生成的实例才会 fulfilled。 只要有一个 Promise 实例的状态变成 rejected,那么 Promise.all 生成的实例就会 rejected

Promise.race

  • 做用:与 Promise.all 相似,也是将多个 Promise 实例包装成一个 Promise 实例。

  • 参数:与 Promise.all 相同

  • 特色:

Promise.race 方法生成的 Promise 实例的状态取决于其所包装的全部 Promise 实例中状态最早改变的那个 Promise 实例的状态。

race 函数返回一个 Promise,它将与第一个传递的 promise 相同的完成方式被完成。它能够是完成( resolves),也能够是失败(rejects),这要取决于第一个完成的方式是两个中的哪一个。 若是传的迭代是空的,则返回的 promise 将永远等待。 若是迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,则 Promise.race 将解析为迭代中找到的第一个值。

  • 例子:请求超时
const promise = Promise.race([
    getData('/path/data'),
    new Promise((resolve, reject) => {
        setTimeout(() => { reject('timeout') }, 10000)
    })
])

promise.then(res => console.log(res))
promise.catch(msg => console.log(msg))
复制代码

Promise.resolve()

  • 做用:将现有对象(或者原始值)转为 Promise 对象。

  • 参数:参数能够是任意类型,不一样的参数其行为不一样

    • 若是参数是一个 Promise 对象,则原封不动返回
    • 若是参数是一个 thenable 对象(即带有 then 方法的对象),则 Promise.resolve 会将其转为 Promise 对象并当即执行 then 方法
    • 若是参数是一个普通对象或原始值,则 Promise.resolve 会将其包装成 Promise 对象,状态为 fulfilled
    • 不带参数,则直接返回一个状态为 fulfilledPromise 对象

Promise.reject()

  • 概述

Promise.reject(reason)方法返回一个带有拒绝缘由reason参数的Promise对象。

通常经过使用Error的实例获取错误缘由reason对调试和选择性错误捕捉颇有帮助。

  • 参数:任意参数,该参数将做为失败的理由:
Promise.reject('err')

// 等价于
new Promise(function (resolve, reject) {
    reject('err')
})
复制代码

统一使用Promise

其实咱们在js中能够将同步代码也可以使用Promise

function a() {
  console.log('aaa')
}

// 等价于
const p = new Promise((resolve, rejext) => {
  resolve(a())
})
复制代码

That's All

或者点击Promise

相关文章
相关标签/搜索