JavaScript - Promise 学习笔记一

Promise 是个啥?

马上拿起来了一个有道词典查了一下.

Promise翻译

承诺,保证的意思.
很多电影里都有这句台词:I Promise You.


是什么问题导致了ES6要给一个Promise?

在开发web应用的过程中,AJAX异步请求数据很常见.

$.get('./data/users/1',function(data){
    //.....
})

一个页面请求多个接口,每个接口之间有相互依赖关系也很常见.

$.get('./data/users/1',function(data){
    $.get('./data/products/${data.userid}',function(data){
        $.get('./data/accountInfo/${data.productAccountInfo}',function(data){
            //......
        })
    })
})

有问题吗? callback 被丢到了 Event Loop 里了,执行时机除了它自己,没人能知道.所以,为了获取上一个接口的数,我们需要在它里面在发送下一个 Ajax 请求.

强行问题在于:如果这样的接口依赖很多呢?

所谓的callback hell --- 回调地狱??

callback hell

对,代码从原来的线性自上而下,变成了现在的横向发展.

但不管它们变成什么样.

起码在上述和我简单的demo中的代码,有几个共同点

  • 都是 callback 回调的方法.
  • 都是下一个接口请求依赖上一个接口返回的数据.

Promise 如何解决代上述代码横向发展的问题的?

为了解决代码丑陋和难以维护(代码写的太臃肿,太丑了,确实也就变得很难维护了)问题.

于是 ES6 新推出了一个叫 Promise 的玩意.

感性认知一下


userDataPromise('./data/users/1')
    .then((data)=>{
        // 在这里拿到了用户数据
       return productsDataPrmoise('./data/products/${data.userid}')
    })
    .then((data)=>{
        // 在这里拿到的商品信息
        return accountDataPromise('./data/accountInfo/${data.productAccountInfo}')
    })
    .then((data)=>{
        // 在这里拿到了账户信息,然后该做什么就作什么.
        //.....
    })

这个和上述使用 $.get 的共同点.

  • 都是一个请求发完之后,接着发下一个请求.下一个请求依赖上一个请求的数据.
  • 每个 Promise使用 then???来执行回调函数.
  • 代码的结构是很舒服的纵向发展.

这个和上述 $.get 的不同点

  • $.get 的回调函数,我们是在一个方法(,function(){callbackhere})里写的.
  • 第二个$.get嵌套在了第一个$.get的callback里面.
  • 代码是横向发展的

仅仅是代码从嵌套横向变成了then纵向了而已啊?那我把回调函数放在外面赋值,不给里面不就行了?


function get(url) {
      var oReq = new XMLHttpRequest()
      // oReq.onload = function () {
      //   callback(JSON.parse(oReq.responseText))
      // }
      oReq.open('GET', url, true)
      oReq.send()

      return oReq // 返回这个是为了拿到 onload & oReq.responseText
}



var xhr = get('./data/1.json')
// 把异步callback注册到Event Loop 的操作仍然是同步的,t同步的再慢,也比异步的要快.我就不相信会出现,我xhr2 还没执行完,xhr 的 onload 回调函数就执行了的情况!!!

// 同步代码的执行优先权永远大于异步执行代码.
    xhr.onload = ()=>{
      console.log(JSON.parse(xhr.responseText))
    }
    var xhr2 = get('./data/2.json')
    xhr2.onload = () => {
      console.log(JSON.parse(xhr2.responseText))
    }
    var xhr3 = get('./data/3.json')
    xhr3.onload = () => {
      console.log(JSON.parse(xhr3.responseText))
    }    
// 对比
$.get('./data/users/1',function(data){
    $.get('./data/products/${data.userid}',function(data){
        $.get('./data/accountInfo/${data.productAccountInfo}',function(data){
            //......
        })
    })
})


// 无非就是把异步函数注册代码的步骤平移了出来,仅此而已.


Promise的出现原因之一,就是为了让我们不要在写那种 callback hell 那样的代码结构了吗?

Promise 的基本使用.

Promise 是解决啥的?
解决多个异步回调函数之间嵌套写法的问题
意思就说,如果没有异步,都是同步的代码,就用不上Promise了

所以,用Promise主要是用在异步上.

可以Promise想象成一个异步操作的容器.
Promise承诺你,当这个异步完成之后,不管成功还是失败,只要你指定了对应的回调函数,我都会去执行.

是不是感觉很废?

  • 异步操作,我要装在你里面去
  • 什么是成功,什么是失败我也要告诉你.

但为了解决多个异步嵌套导致代码横向扩展的问题,咱们还是去用吧.毕竟逼格高一点.

  • 首先我们需要实例化一个Promise对象(对Promise是一个构造函数)
new Promise()
  • 此构造函数接受一个函数作为参数.
new Prmoise(function....)
  • 此函数参数包含两个形参(reslove,reject)
new Promise(function(reslove,reject){})
  • 最后是完全体
new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})
  • 要把异步操作给 Promise ==> $.get('./data/users/1'
  • 告诉Promise啥是成功 ==> reslove(data)
  • 啥是失败 ==> reject(data.error)

接着就是使用我们刚new出来的Promise.

既然是new出来的,我们就拿个对象去接受.

var p = new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})

我们在给Promise传参的时候,指定了 reslove & reject 两个函数的形参,如何传递这两个的实参呢?

p.then((data)=>{},(err)=>{})

使用实例对象的 then 方法,接受两个参数,顺序是 then(resloveCallback,rejectCallback)

最后完整的使用,并发送请求.

var p = new Promise(function(reslove,reject){
    $.get('./data/users/1',function(data){
        if (data.success) reslove(data)
        reject(data.error)
    })
})

p.then((data)=>{
    console.log(data) // 数据请求成功
},(err)=>{
    console.log(err) // 数据请求失败.
})

到这一步是不是很无聊?
无非就是提供了一个then方法,让我们来指定成功和失败的回调函数实参.....

Promise 的一些其他特性.

官方说明:

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

Promise对象有以下两个特点。

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

根据文档画一张很无聊的图.

Promise状态变化

Promise 的基本套路

还是直接上代码来的实际.


promise的then方法返回的仍然是个一个 Promise对象

由于 Promise 的 then 方法,又返回了一个 Promise 对象的类型,所以,Promise对象可以不停的then.

这就为 Promise 对象链式调用提供了基础.

function createReslovePromise() {
  return new Promise(function (reslove,reject) {
    setTimeout(function () {
      reslove(data)
    },1000)
  })
}

createReslovePromise()
  .then(() => {
    return 1
  })
  .then((data) => {
    console.log(data)
    return 2
  })
  .then((data) => {
    console.log(data)
  })


relax-2:测试 relax$ node Promise.js
1
2
relax-2:测试 relax$

图解:

15440308685957.jpg

上一个 thenreslove 的返回值,就是下一个thenreslove的参数

但是如果,,我在上一个Promise对象thenreslove
中,成功接受到了成功数据之后,就返回下一个 Promise对象.

那么下一个then就是上一个返回的Promise对象的then了.

function createReslovePromise(stepName) {
  return new Promise(function (reslove,reject) {
    setTimeout(function () {
      reslove(stepName)
    },1000)
  })
}

createReslovePromise('step 01')
  .then((data) => {
    console.log(data) // 数据收到了,返回下一个Promise
    return createReslovePromise('step 02')
  })
  .then((data) => {
    console.log(data)
    return createReslovePromise('step 03')
  })
  .then((data) => {
    console.log(data)
  })

relax-2:测试 relax$ node Promise.js
step 01
step 02
step 03

图解

15440311984996.jpg

基于上面这个then返回 Promise的特性,就可以完成链式的之上而下的异步代码结构了.

使用Promise请求一个瞎编的逻辑

  • 首先请求 ./data/users/1 拿到id为1的用户
  • 接着请求 ./data/preferences/{user.preferences} 在根据拿到用户的 preferences 拿到用户的偏好设置里面的bobbies
  • 最后根据用户的preferences.hobbies 拿到用户的爱好.../data/hobbies/{preferences.hobbies}
{
  "users":[
    {"id":1,"name":"张三","preferences":1},
    {"id":2,"name":"李四","preferences":2},
    {"id":3,"name":"王五","preferences":3},
    {"id":4,"name":"赵六","preferences":4}
  ],
  "preferences":[
    {"id":1,"hobbies":1},
    {"id":2,"hobbies":2},
    {"id":3,"hobbies":3},
    {"id":4,"hobbies":4}
  ],
  "hobbies":[
    {"id":1,"values":["看书,打游戏,听歌,骑行"]},
    {"id":2,"values":["看书,打游戏,听歌,骑行"]},
    {"id":3,"values":["看书,打游戏,听歌,骑行"]},
    {"id":4,"values":["看书,打游戏,听歌,骑行"]}
  ]
}

启动一个 json-server 服务

json-server --watch db.json

服务启动成功

15440695886304.jpg

测试一下


15440696295064.jpg

使用传统的 $.get 方式


 $.get('http://localhost:8000/users/1',function(data){
      let userInfo = data
      console.log(data)
      $.get(`http://localhost:8000/preferences/${userInfo.preferences}`,function(data){
        console.log(data)
        let userPreferences = data
        $.get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`,function(data){
          let userHobbies = data
          console.log(data)
          var hobbies = userHobbies.values && userHobbies.values.join(',')
          document.querySelector('h1').innerText = hobbies
        })
      })
    }) 
15440703375702.jpg

好像有callback hell回调地狱了.

使用原生XMLHttpRequest回调函数平移出来的方式


var xhr = get('http://localhost:8000/users/1')
xhr.onload = function () {
    let jsonData = JSON.parse(xhr.responseText)
    let userInfo = jsonData
    console.log(jsonData)
    var xhr2 = get(`http://localhost:8000/preferences/${userInfo.preferences}`)
      xhr2.onload = () => {
        let jsonData = JSON.parse(xhr2.responseText)
        let userPreferences = jsonData
        console.log(userPreferences)

        var xhr3 = get(`http://localhost:8000/hobbies/${userPreferences.hobbies}`)
        xhr3.onload = () => {
          let jsonData = JSON.parse(xhr3.responseText)
          let userHobbies = jsonData
          var hobbies = userHobbies.values && userHobbies.values.join(',')
          document.querySelector('h1').innerText = hobbies
        }

      }
    }



    function get(url) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.send()

      return xhr
    }

突然发现问题出来了,如果有依赖性的接口请求关系,不管怎么把回调函数移出来,最后还是会被迫的写成 callback hell 的形式.

最后在使用 Promise

function pGet(url) {
      return new Promise(function (reslove, reject) {
        get(url, function (data) {
          reslove(data)
        })
      })
    }

    function get(url, callback) {
      let xhr = new XMLHttpRequest()
      xhr.open('GET', url, true)
      xhr.send()
      xhr.onload = function () {
        callback && typeof callback === 'function' && callback(JSON.parse(xhr.responseText))
      }
    }

    pGet('http://localhost:8000/users/1')
      .then(function(data){
        console.log(data)
        let userInfo = data
        return pGet(`http://localhost:8000/preferences/${userInfo.preferences}`)
      })
      .then((data)=>{
        console.log(data)
        let preferences = data
        return pGet(`http://localhost:8000/hobbies/${preferences.hobbies}`)
      })
      .then((data)=>{
        console.log(data)
        let hobbies = data
        var hobbiesStr = hobbies && hobbies.values instanceof Array && hobbies.values.join(',')
        document.querySelector('h1').innerText = hobbiesStr
      })

发现代码最终是按照竖向的走向往下写的.
确实是避免了代码横向走的问题.

使用Promise确实可以比较优雅的写出有依赖关系接口方法的代码层级结构.所以,它确实解决了callback hell写法的问题.

你可能感兴趣的:(JavaScript - Promise 学习笔记一)