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写法的问题.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • title: promise总结 总结在前 前言 下文类似 Promise#then、Promise#resolv...
    JyLie阅读 12,219评论 1 21
  • Promiese 简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果,语法上说,Pr...
    雨飞飞雨阅读 3,348评论 0 19
  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,297评论 5 22
  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,698评论 1 56
  • 读《运营之光》有感 我一直觉得读书笔记的精髓是通过阅读一本书,除了学习书中的知识体系,还能够结合自身情况进行思考和...
    猫男呓语阅读 332评论 0 0