ES6 promise 用法小结

ES6 promise 用法小结

Js 是一⻔单线程语言,早期解决异步问题,大部分是通过回调函数进行。

比如我们发送 ajax 请求,就是常见的一个异步场景,发送请求后,一段时间服务器给我们响应,然后才拿到结果。如果我们希望在异步结束之后执行某个操作,就只能通过回调函数的方式进行操作

const request = function (callback) {
  setTimeout(function () {
    callback()
  }, 1000)
}
request(function () {
  console.log(123)
})
// 以上代码执行结果:1s 后输出 123
// request 就是一个异步函数,里面执行的 setTimeout 会在 1s 之后调用传入的 callback 函数, 
// 如果后续还有内容需要在异步函数结束时输出,就需要多个异步函数进行嵌套,非常不利于后续的维护。
setTimeout(function () {
  console.log(123)
  setTimeout(function () {
    console.log(321)
    // ...
  }, 2000)
}, 1000)

为了使回调函数以更优雅的方式进行调用,在 ES6 中引入了 promise,让异步 操作的变得「同步化」。

1,Promise 基础

通过 new Promise() 即可构造一个 promise 实例,这个构造函数接受一个函数,接受两个参数:resolvereject,代表改变实例的状态到 已完成 或是 已拒绝

const promise = new Promise(function (resolve, reject) {
  console.log('promise called')
  setTimeout(function () {
    resolve()
  }, 3000)
})

promise.then(function () {
  console.log('promise resolve callback')
})

// 先打印出 promise called, 3s 后打印 promise resolve callback
function promise1() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log('1s后输出')
      resolve()
    }, 1000)
  })
}

function promise2() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log('2s后输出')
      resolve()
    }, 2000)
  })
}
// 以下两个promise实例,串联起来即可写为:
promise1().then(function () {
  return promise2()
})
或
promise1().then(promise2)

控制台打印结果:1s之后出现 1s后输出,再经过2s出现2s后输出。实例中,当前promise如果状态变为已完成(执行resolve方法),就会去执行 then 方法中的下一个 promise 函数。同样的如果promise变成已拒绝状态(执行reject方法),就会进入后续的异常处理函数中。

function promise3() {
  return new Promise(function (resolve, reject) {
    var random = Math.random() * 10 // 随机一个 1 - 10的数字
    setTimeout(function () {
      if (random >= 5) {
        resolve(random) // 把随机生成的数字传给了 resolve, 在 then 中可以拿到这个值
      } else {
        reject(random)  // 把随机生成的数字传给了 reject,在 then 中可以拿到这个值
      }
    }, 1000)
  })
}

var onResolve = function (val) {  
  console.log('已完成:输出的数字是:', val)
}

var onReject = function (val) {
  console.log('已拒绝:输出的数字是:', val)
}

// promise 的then也可以接受两个参数,第一个参数是 resolve 后执行的,第二个参数是 reject 后执行的
promise3().then(onResolve, onReject)

// 也可以通过 .catch 方法拦截状态变为已拒绝时的 promise
promise3().catch(onReject).then(onResolve)

// 也可以通过 try catch 进行拦截状态变为已拒绝的 promise
try {
  promise3().then(onResolve)
} catch (e) {
  onReject(e)
}

以上使用3种方式拦截最终变为「已拒绝」状态的 promise,分别是使用 then 的第二个参数,使用 .catch 方法捕获前方 promise 抛出的异常,使用 try catch 拦截代码块中 promise 抛出的异常

我们可以发现,在改变 promise 状态时调用 resolvereject 函数的时候,可以给下一步 then 中执行的函数传递参数。

2,封装异步操作为promise

我们可以将任何接受回调的函数封装为一个promise, 实例:

// 原函数
function func(callback) {
  setTimeout(function () {
    console.log('1s 后显示')
    callback()
  }, 1000)
}

var callback = function () {
  console.log('在异步结束后打印')
}
// 用传入回调函数的方式执行
func(callback)

image

以上实例是最传统的使用传入回调函数的方式在异步结束后执行函数。我们可以通过封装 promise的方式,将这个异步函数变为 promise:

function func() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log('1s 后显示')
      resolve()
    })
  })
}

var callback = function () {
  console.log('在异步结束后打印')
}

func().then(function () {
  callback()
})

再比如,我们发送 ajax 请求也可以封装为 promise:

function ajax(url, success, fail) {
  var client = new XMLHttpRequest();
  client.open('GET', url)
  client.onreadystatechange = function () {
    if (this.readyState !== 4) {
    // this.readyState扩展:
    // 0: 未初始化,还没调用 send() 方法
    // 1: 载入,已调用send()方法,正在发送请求
    // 2: 载入完成,send()执行完毕,已接受全部响应内容
    // 3: 交互,正在解析响应内容
    // 4: 完成,响应内容解析完成,可以直接使用responseText数据
      return
    }
    if (this.status === 200) {
      success(this.response)
    } else {
      fail(new Error(this.statusText))
    }
  }
  client.send()
}

ajax('http://localhost:8080/home/swiper', function (res) {
  console.log('成功')
  console.log(res)
}, function (err) {
  console.log('失败', err)
})

image

以上 ajax 请求,通过封装 promise 的方式,在原来的执行回调函数的地方,更改当前 promise的状态,就可以通过链式调用:

function ajax(url) {
  return new Promise(function (resolve, reject) {
    var ct = new XMLHttpRequest();
    ct.open('GET', url)
    ct.onreadystatechange = function () {
      if (this.readyState !== 4) {
        return
      }
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    ct.send()
  })
}

ajax('http://localhost:8080/home/swiper').catch(function () {
  console.log('失败')
}).then(function (res) {
  console.log('成功')
  console.log(res)
})

我们可以把任何一个函数或者是异步函数改为promise,尤其是异步函数,改为 promise中后即可进行链式调用,增强可读性

3,小总结

  • 1,promise 有三种状态,进行中(Pending)已完成(Fulfilled)已拒绝(Rejected),进行中状态可以更改为已完成 或 已拒绝,已经更改过状态后无法继续更改(例如从已完成改为已拒绝)。

  • 2,ES6 中的 Promise 构造函数,我们构造之后需要传入一个函数,他接受两个函数参数,执行第一个参数之后就会改变当前 promise 为已完成状态,执行第二个参数之后就会变为 已拒绝 状态。

  • 3,必须有一个then方法用以访问其当前值和原因。promise的 then 方法接受两个参数:promise.then(onFulfilled, onRejected) 他们都是可选参数,他们都是函数。如果 onFulfilledonRejected 不是函数,则需要忽略他们

  • 4,已拒绝的 promise,后续可以通过 .catch 方法或是 .then 方法的第二个参数或是 try catch 进行捕获。

  • 5,then方法可以被同一个promise调用多次。

    • 当 promise 成功执行的时候,所有的 onFulfilled 需按照其注册顺序依次回调
    • 当 promise 被拒绝执行的时候,所有的 onRejected 需按照其注册顺序依次回调
      then 方法必须返回一个 promise 对象: promise2 = promise1.then(onFulfilled, onRejected)
    • 只要 onFulfilled 或者 onRejected 返回一个值 xpromise2 都会进入 onFulfilled 状态
    • 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
    • 如果 onFulfilled 不是函数且 promise1 状态变为已完成, promise2 必须成功执行并返回相同的值
    • 如果 onRejected 不是函数且 promise1 状态变为已拒绝, promise2 必须执行拒绝回调并返回相同的据因
var promise1 = new Promise((resolve, reject) => {
  reject()
})
promise1
  .then(null, function () {
    return 123
  })
  .then(null, null)
  .then(null, null)
  .then(
    () => {
      console.log('promise2 已完成')
    },
    () => {
      console.log('promise2 已拒绝')
    })

以上代码输出:promise2 已完成

以上代码可改写为:

var promise1 = new Promise(function (resolve, reject) { reject() })
var promise2 = promise1.then(null, function () { return 123 })
var promise3 = promise2.then(null, null) // 如果 onFulfilled 不是函数且 promise2 状态变为已完成, promise3 必须成功执行并返回和 promise2 相同的值, 即 123
var promise4 = promise3.then(null, null) // 同理,promise4 也能拿到 123 的值
promise4
  .then(val => {
    console.log('promise2 已完成', val)  // promise2 已完成 123
  }, () => {
    console.log('promise2 已拒绝')
  })

实例:

var promise1 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(1)
      resolve()
    }, 1000)
  })
}

var promise2 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2)
      resolve()
    }, 2000)
  })
}

promise1()
  .then(function () {
    return promise2()  // 此处返回一个 promise 实例
  })
  .then(function () {
    console.log('已完成')
  }, function () {
    console.log('已拒绝')
  })
image

4, promise 构造函数上的 静态方法

  • 4.1, promise.resolve

返回一个 promise 实例,并将它的状态设置为已完成,同时将他的结果作为传入 promise 实例的值

var promise = Promise.resolve(123)
promise.then(function (val) {
  console.log('已完成', val)
})
image

Promise.resolve 的参数也可以处理对象、函数等内容

  • 4.2,promise.reject

返回一个 promise 实例,并将它的状态设置为已拒绝,同时也将他的结果作为原因传入 onRejected 函数

var promise = Promise.reject(123)
promise.then(null, function (val) {
  console.log('已拒绝', val)
})
image
  • 4.3,Promise.all

返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当所有 promise 实例都成 已完成 状态时,进入已完成状态,否则进入已拒绝状态。

var promise1 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(1)
      resolve()
    }, 1000)
  })
}

var promise2 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2)
      resolve()
    }, 1000)
  })
}

Promise.all([promise1(), promise2()]).then(function () {
  console.log('全部 promise 均已完成')
})
image

以上代码为多个 promise 同时进行,等待 1s 打印 1 之后,再等待 1s 就 会打印 2 和全部 promise 均已完成。

  • 4.4,Promise.race
    返回一个 promise 实例,接受一个数组,里面含有多个 promise 实例,当有一个 promise 实例状态改变时,就进入该状态且不可改变。这里所有的 promise 实例为竞争关系,只选择第一个进入改变状态的 promise 的值。
var promise1 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(1)
      resolve(1)
    }, 1000)
  })
}

var promise2 = function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2)
      resolve(2)
    }, 1000)
  })
}

Promise.race([promise1(), promise2()]).then(function (val) {
  console.log('有一个 promise 状态已经改变', val)
})
image

5, generator / async await

ES6 之后,我们可以使用 generator 和 async/await 来操作 promise,比起使用 promise 串行的调用来说,从语法层面 让调用关系 显得更加串行。

function promise1() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(1)
      resolve()
    }, 1000)
  })
}

function promise2() {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log(2)
      resolve()
    }, 1000)
  })
}

// 使用 generator 函数
function* gen() {
  yield promise1()
  yield promise2()
}
var g = gen()
g.next()
g.next() // 1 2

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

推荐阅读更多精彩内容