你不知道的JavaScript :Promise 与 Async/Await

Winer

前言

对于JavaScript这门语言,其实我更喜欢称它为ECMAScript,从一开始我们就已经涉及到异步编程,但是多数JavaScript开发者从来没有认真思考过自己程序中的异步,到底是怎么实现的,以及为什么会出现。但是由于开发者对JavaScript的需求和项目的复杂程度日渐扩大,特别是对异步的管理越来越令人痛苦,这一切导致我们迫切需要更加强大、更加合理的异步方法,帮我们管理异步的状态。

开始

在JavaScript异步编程历史上,我认为一共出现了三种异步编程方式

  • 回调
  • Promise
  • Async/Await

由于我不想回忆起很久以前,被回调地狱支配的恐惧,就跳过回调这一块,读者们自行了解(篇幅有限)
(PS:好吧,new Date() --> Sat May 19 2018 23:55:17 GMT+0800 (中国标准时间),写完洗洗睡吧)

Promise

什么是Promise

Promise是一种范式。在这里扯一句:回调是将我们封装的回调函数交给第三方(甚至可能是外部代码),紧接着我们期待它能够调用我们封装的回调函数。那么Promise就是不把自己的程序传递给第三方,而是第三方给我们提供此任务何时结束,然后由我们自己决定下一步做什么

设想一个场景,今天早上,我去买早餐,到了一个快餐店,点了一个汉堡做了一次请求),我就在收银台上支付了这个汉堡的价格类似于参数的传递),但是我并没有立即得到这个汉堡,而是拿到了一个订单号,这个时候我就只需要坐着等待小姐姐叫到我的订单号(这个时候已经产生了一个Promise),在这个中间等待的时间,我们可以做一些其他的事情,比如打电话、玩儿手机、谈工作等等......(这就是合理利用中间的空隙时间,在js的异步中也可以完全体现,但不在本文的探讨范围内),直到小姐姐叫到XX号码的好了(一个Promise执行结束),结果一般有两种,要么是汉堡做好了Promise中的resolve()中设置的值),要么是汉堡卖完了(Promise中的reject()中设置的值),这时我就需要考虑换其他食物了。假设汉堡做好了,我再过去拿我的汉堡,拿到过后,我可以自行选择吃掉它或者是扔掉它(当然这是不可能的)(这就体现了具体怎么实现的决定权在我们)
这个例子我觉得还是很形象的,哈哈哈。

如何创建并使用一个Promise

首先我先告诉你Promise的决议结果resovle完成reject拒绝

  // -----------------------> Promise 的创建与使用
  function creat_promise () {
    return new Promise((resolve, reject) => {
      resolve(42)
    })
  }
  function use_promise () {
    const a_promise = creat_promise()
    a_promise.then(res => console.log(res))
  }
  use_promise()

Promise的高级使用方法

当你看到这一节时,需要用到自定义的模拟数据请求类
该api类在这儿
或者查看本项目的源码,配合着浏览本文章

// api.js
// 定义初始数据
const users = [
  { id: 1, name: 'jack', year: 12, grade: 1 },
  { id: 2, name: 'john', year: 12, grade: 1 },
  { id: 3, name: 'winer', year: 12, grade: 2 }
]

// user的father,根据child链接
const fathers = [
  {id: 11, child: 'jack', name: 'jack_father'},
  {id: 22, child: 'john', name: 'john_father'},
  {id: 33, child: 'winer', name: 'winer_father'}
]

class Api {
  // 根据id获取一个User对象的Promise
  getUserById (id, request_time = 1000, fn = function () {}) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const user = users.find(item => {
          return item.id === id
        })
        fn()
        resolve(user)
      }, request_time)
    })
  }
  // 根据grade获取一个User对象列表的Promise
  getUsersByGrade (grade) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const _users = users.filter(item => {
          return item.grade === grade
        })
        resolve(_users)
      }, 1000)
    })
  }
  // 根据user获取一个UserName的Promise
  getUserName (user, request_time = 1000) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const child = users.find(item => {
          return item.name === user.name
        })
        resolve(child.name)
      }, request_time)
    })
  }
  // 根据userName获取一个Father的Promise
  getFatherByChildName (childName) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const father = fathers.find(item => {
          return item.child === childName
        })
        resolve(father)
      }, 1000)
    })
  }
  // 抛出一个异常的Promise
  throw_Error () {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('api.js-------->抛出了一个错误'))
      }, 1000)
    })
  }
}

首先 const api = new Api()

  • Promise 的链式顺序模式

    // -----------------------> Promise 的链式顺序模式
      function promise_chain () {
        api.getUserById(1)
          .then(res => {
            console.log(res)
            return api.getUserName(res)
          })
          .then(res => {
            console.log(res)
            return api.getFatherByChildName(res)
          })
          .then(res => console.log(res))
      }
      promise_chain()

    每个Promise.then(..)中的返回值(即使返回的不是Promise对象,因为Promise内在机制会将其转换为一个可以使用的Promise)将会作为下一个Promise对象,即Promise.then( return ...)得到的是一个Promise对象,因此可以不断地Promise.then( return ... ).then( return ... ).then( return ... ).then( return ... ).then( return ... ).then()......

  • Promise 的catch

    function promise_lastErro () {
        api.throw_Error()
        .then(res => console.log('没有错'))
        .catch(err => {
          console.log(err)
          foo.bar()
        })
        console.log('无法捕捉最后一个catch的错误:foo.bar()')
      }
      promise_lastErro()
  • Promise 的并发/并行 : Promise.all([...])
    传入的参数是一个Promise的数组
    如果传入的是[],Promise.all([...])将立即决议为完成
    全部Promise完成,Promise.all([...])完成
    如果有一个被拒绝,则Promise.all([...])被拒绝

    function promise_concurrent_1 () {
        const p1 = api.getUserById(1,Math.random() * 1000, () => { console.log('p1执行完毕') })
        const p2 = api.getUserById(1,Math.random() * 1000, () => { console.log('p2执行完毕') })
        Promise.all([p1, p2])
          .then(res => console.log('p1 p2 都执行完毕'))
      }
      promise_concurrent_1()
  • Promise 的任务竞争:竞态(第一优先) : Promise.race([...])
    传入的参数是一个Promise的数组
    如果传入的是[],Promise.race([...])将不会决议,始终处于挂起状态
    只要有一个Primise完成,Promise.race([...])则完成
    如果有一个被拒绝,则Promise.race([...])被拒绝

    function promise_compete () {
        const p1 = api.getUserById(1, 2000)
        const p2 = api.getUserById(2)
        Promise.race([p1, p2]).then(res => console.log(res))
      }
      promise_compete()
  • 关于Promise.all([...])与Promise.race([...])的变体有很多

    • Promise.none([...]): 要求[...]全部被拒绝,Promise.none([...])决议为完成
    • Promise.any([...]): 与Promise.all([...])类似,但只要求完成一个即可,但是会执行所有Promise
    • Promise.first([...]):与Promise.any([...])类似,但是只要有第一个Promise决议为完成,就不关心后面的Promise
    • Promise.last([...]):与Promise.first([...])相反,最后一个完成的胜出

到此Promise的基本用法就是这些了吧,不知不觉半个小时过去了。

Async/Await

什么是Async/Await

我认为Async/Await是区别于Promise更优雅的体现,它可以简化Promise的大部分代码,让你的代码看上去优雅美观并且大气。并且我支持你,在现在甚至以后,对异步的管理尽可能使用Async/Await。

Async/Await的使用

  • 使用Async/Await代替链式promise(类比 ---> Promise的链式顺序模式)

    async function async_Request () {
        console.log('请稍等..此时是三个setTimeOut,每个1s,需要等待3s')
        const user = await api.getUserById(1)
        const userName = await api.getUserName(user)
        const father = await api.getFatherByChildName(userName)
        console.log(father)
    }
    async_Request()
  • 使用Async/Await的并发与并行

    async function async_Concurrent () {
        const users = await api.getUsersByGrade(1)
        const usersPromise = users.map(item => api.getUserName(item, Math.random() * 1000))
        Promise.all(usersPromise).then(res => {
          console.log(res)
        })
    }
    async_Concurrent()
  • 使用Async/Await的错误捕捉

    async function async_CatchErro () {
      try {
        await api.throw_Error()
        console.log('未捕捉到错误?')
      } catch (error) {
        console.log(error)
      }
    }
    async_CatchErro()
  • Async/Await函数的互相调用

    async function async_A () {
        const user = await api.getUserById(2)
        const userName = await api.getUserName(user)
        const father = await api.getFatherByChildName(userName)
        return { user, userName, father }
    }
    async function async_B () {
        console.log('数据获取中...')
        const { user, userName, father } = await async_A()
        console.log('userInfo',{ user, userName, father })
    }
    async_B()
  • Async/Await检索十条数据,串行

    async function async_ten_serial (length = 10) {
        try {
          const users = []
          console.log('串行请求10条数据,每条1秒,请稍等10秒钟....')
          while(users.length < 10) {
            users.push(await api.getUserById(1))
          }
          console.log(users)
        } catch (error) {
          console.log(error)
        }
    }
    async_ten_serial()
  • Async/Await检索十条数据,并行

    async function async_ten_parallel (length = 10) {
        try {
          const usersPromise = []
          console.log('并行请求10条数据,每条1秒,请稍等1秒钟....')
          while(usersPromise.length < 10) {
            usersPromise.push(api.getUserById(2))
          }
          const users = await Promise.all(usersPromise)
          console.log(users)
        } catch (error) {
          console.log(error)
        }
    }
    async_ten_parallel()

ok!本文并没有讲那些概念性的东西,只是简单地讲这几种实现用代码描述出来,更详细的。请大家参考官方文档,其实对于这篇文章的排版,我发现应该将PromiseAsync/Await对比起来描述,懒得重新排版了,委屈各位手动对比了。(:
源码地址
(PS: newDate() ---> Sun May 20 2018 00:42:15 GMT+0800 (中国标准时间))
不知不觉竟然写了接近一个小时,溜了溜了,不修仙。

阅读 2.7k

你不知道的JavaScript系列
JavaScript这门语言,不解释...特别是其中类似于魔法的语法与特性,总是让我惊喜。

一入前端深似海

455 声望
200 粉丝
0 条评论

一入前端深似海

455 声望
200 粉丝
文章目录
宣传栏