ES6 Promise

ES6 Promise

 

promise /ˈprɑːmɪs/ 承诺;许诺;保证

Promise的含义

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

我们来模拟传统的解决方案

const url = 'https://jsonplaceholder.typicode.com/todos'
// 它是由浏览器提供的,不是JavaScript原生的
const xhr = new XMLHttpRequest()
let btnDom = document.getElementById('btn')
const handler = function() {
  
}
// 绑定点击事件
btnDom.onclick = function () {
  xhr.onreadysrtatechange = fucntion() {
    // readyState表示请求/响应过程中当前的活动阶段0 1 2 3 4
    if (xhr.readyState === 4) {
      if (xhr.status >=200 && xhr.status <= 300) {
        const data = xhr.responseText;
        // 拿到数据后如果还需要在做发送请求的动作
        // 我们就需要在onreadysrtatechange的回调中继续写代码
        // 这样代码就会嵌套很多层 也被称为回调地狱
      } else {
        console.log('Response was unsuccessful ' + xhr.status)
      }
    }
  }
  xhr.open('get', 'http://www.baidu.com')
  xhr.setRequesetHeader('customHeader', 'customHeaderValue')
  // send的参数不能为空,get请求传递null
  xhr.send(null)
}

所谓Promise,简单来说就是一个容器,里面保存着某个未来才会结束的事件的结果(通常是一个异步操作)

它有两个特点:

  1. 对象的状态不受外界的影响。它有3个状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)只有异步操作的结果「 (resolve, reject) => { 异步操作返回结果后调用 resolve(value) 或者 reject(err) } 」可以决定当前是哪种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来。
  2. 一旦状态发生改变,就不会再变。任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从 pending 到 fulfilled 和 从 pending 到 rejected

它也有一些缺点:

  1. 无法取消Promise,一旦新建它会立即执行,无法中途取消。
  2. 如果不设置回调函数「.then() 和 .catch() 」Promise内部抛出的错误,不会反应到外部。

ES6规定,Promise是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve, rejecte) {
  // some code
  if (/*异步操作成功*/) {
    resolve(value)
  } else {
    reject(error)
  }
})

Promise构造函数 接受一个函数作为参数,该函数 的 两个参数 分别是 resolve 和 reject ,它们又是两个函数,由JavaScript引擎提供,不用自己部署。

resolve函数的作用是,将 Promise 对象的状态 从 pending 变为 resolved 。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。

reject函数的作用是,将 Promise 对象的状态 从 pending 变为 rejecetd 。在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用 then 方法指定 resolved 状态 和 rejected 状态 的回调函数

promise.then(res => {
  // success
}, err => {
  // error
)

then方法接受两个回调函数作为参数。

第一个回调函数是Promise对象的状态变为resolved时调用
第二个回调函数是Promise对象的状态变为rejected时调用

这两个函数都是可选的,不一定要提供。它们都接受Promise对象传出的值( res 和 err )作为参数。

function timeout(ms) {
  // 返回Promise实例对象 用来.then后续的操作
  return new Promise((resolve, rejecte) = > {
    setTimeout(() => {
      resolve('success setTimeout ' + ms)
    }, ms)
    // 简写
    setTimeout(resolve, ms)
  })
}
// 过了ms后,Promise实例的状态变为resolved
// 就会触发.then方法绑定的回调函数
timeout(3000).then(res => {
  console.log(res) // success setTimeout 3000
})

Promise 新建后就会立即执行

let promise = new Promse((resolve, reject) => {
  console.log('Promise')
  resolve()
})
promise.then(() => {
  console.log('resolved')
})
console.log('hello')
// 'Promise'
// 'hello'
// 'resolved'

异步加载图片的例子

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const image = new Image()
    image.onload = function() {
      resolve(image)
    }
    image.onerror = function() {
      reject(new Error('Could not load image at ' + url))
    }
    image.src = url
  })
}
// 调用异步加载图片的方法
loadImage('https://p7.itc.cn/images01/20201104/87d0aa991d454770813dcb01260c2ed0.jpeg')
.then(res => {
  // res <img src='https://p7.itc.cn/images01/20201104/87d0aa991d454770813dcb01260c2ed0.jpeg'>
  console.log('res: ', res)
  // 可以插入到页面某个位置
}).catch(error => {
  console.log('error: ', error)
})

用Promise实现Ajax操作的例子

const getJSON = function(url) {
  return new Promise((resolve, rejected) => {
    const jsx = new XMLHttpRequest()
    const handler = function() {
      if (this.readyState !== 4) return
      if (this.status >= 200 && this.status <= 300) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    jsx.open('get', url)
    jsx.onreadystatechange = handler;
    jsx.responseType = 'json'
    jsx.send(null) 
  })
}
getJSON('/post.json').then(json => {
  console.log(json)
}, error => {
  console.log(error)
})

Promise.prototype.then()

Promise实例具有then方法,它的then方法是定义在原型对象 Promise.prototype 上的

then方法如果返回一个新的Promise实例(不是原来那个实例)因此可以采用链式写法,即then方法后面可以再调用另一个then方法

getJSON('/post.json').then(json => {
  return getJSON('/post.json?id=' + json.id)
  // return getJSON(json.conmmentUrl)
}, error => {
  console.log(error)
}).then(content => {
  console.log(content)
})

Promise.prototype.catch()

一般来说,不要在then方法里面定义 rejected 状态的回调函数,总是使用catch方法

因为catch这种写法不仅可以捕获到 rejected 状态的错误,还可以捕获前面then方法执行中的错误,并且也更接近嗯同步的写法「try / catch」

跟传统的 try / catch 代码块不同的是,如果没有使用 catch 方法指定错误处理的回调函数, Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。通俗的说法就是'Promise 会吃掉错误'

const someThing = function() {
  return new Promise((resolve, reject) => {
    // 这行会报错,因为x没有声明
    resolve(x + 2)
  })
}
someThing().then(res => {
  console.log('everything is great!')
}) 
setTimeout(() => console.log(123), 2000)
// ReferenceError: x is not defined
// 123

上述代码控制台会报出错误,但是不会退出进程、终止脚本执行,2s后还是会输出123

这就是说 Promise 内部的错误不会影响到 Promise 外部的代码,造成 'Promise会吃掉错误'

显然这样的行为是不可取的

所以,也总是建议 Promise 对象后面要跟 catch 方法,这样可以处理 Promise 内部发生的错误。

Promise.prototype.finally()

finally方法用于不管 Promise 对象最后的状态如何,都会执行的操作。

promise
  .then(result => { ... })
  .catch(err => { ... })
  .finally(() => { ... })

上面代码中,不管 promise 最后的状态,在执行完 then 方法 或 catch 方法的回调函数以后,都会执行finally方法指定的回调函数。

finally方法的回调函数不接受任何参数,这意味着没有办法知道前面的 Promise 状态到底是 resolved 还是 rejected ,这表明,finally方法里的操作,应该是与状态无关的,不依赖于Promise执行的结果。

Promise.all()

all方法将拥有多个 Promise 实例,包装成一个新的 Promise 实例

// p1 p2 p3 都是Promise实例 如果不是Promise实例 
// 将会调用Promise.resolve()方法转为Promise实例
const p = Promise.all([p1, p2, p3])

p的状态有两种情况

  1. 只有 p1 p2 p3 的状态都变成 fulfilled ,p的状态才会变成 fulfilled 此时,p1 p2 p3的返回值组成一个数组,传递给p的回调函数
  2. 只要 p1 p2 p3 之中有一个状态被 rejected ,p的状态就会变成 rejected ,此时第一个被 rejected 的实例的返回值,会传递给p的回调函数
const promises = [2, 3, 5, 7, 11, 13].map((id) => {
  return getJSON('post/' + id + '.json')
})
Promises.all(promises)
  .then(posts => { ... })
  .catch(reason => { ... })

catch方法返回的是一个新的 Promise 实例(该实例执行完catch方法后,也会变成resolved),catch后面可以继续链式调用then方法,结合all方法看下

const p1 = new Promise((resolve, reject) => {
  resolve('hello')
})
.then(result => result)
.catch(e => e)

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了')
})
.then(result => result)
.catch(e => e)

Promise.all([p1, p2])
.then(result => console.log(result))
// 不会触发catch方法
.catch(e => console.log(e))
// ['hello', Error:报错了]

因为catch方法返回的是一个新的Promise实例且resolved状态的,所以不会出发all方法的catch方法

Promise.race() 竞赛

race方法同样是将多个Promise实例,包装成一个新的Promise实例

const p = Promise.race([p1, p2, p3])

上面代码中,只要 p1 p2 p3 之中有一个实例率先改变状态,p的状态就跟着改变,那个率先改变的Promise实例的返回值,就传递给p的回调函数

下面的例子,如果一个请求指定时间内没有获取到结果,就抛出超时错误

const p = Promise.race([
  fetch(url),
  setTimeout(() => {
    reject(new Error('time out'))
  }, 5000)
])
p.then(data => {
  console.log(data) // 拿到请求数据data
})
.catch(err => {
  console.log(err) // time out
})

 

发布于 2024-01-04 22:19・IP 属地上海

posted on 2024-04-07 17:30  漫思  阅读(11)  评论(0编辑  收藏  举报

导航