JS基础(一)-畅游promise世界

490 阅读7分钟

本文主要介绍了个人对promise的一些粗浅理解,如有不正确的地方,还请指正。

一. Promise是什么

从JS语法层面上讲,Promise是一个函数,可以通过new关键字进行调用然后生成一个对象,这个对象被成为promise对象。promise对象有三种状态,为pedding、resolved、rejected,这是promise的重要机制。 从功能的角度来讲,Promise是一个容器,它里面包含着一个未来才会得到的(通常是异步)结果。 Promise帮助调用者拿回了调用回调函数的主动权,而不是把主动权交给第三方函数。

二. promise解决了什么问题

2.1 回调的涉及的信任问题

1. 使用回调风格的异步处理

以下关于callback函数的执行有几种信任问题。

function getData(url, callback) {
  let req = new XMLHttpRequest()
  req.open('GET', url, true)
  req.onload = function () {
    if (req.status === 200) {
      // 使用回调处理会存在几种潜在的问题
      // 1. 过早或者过晚调用
      // 2. 干脆不调用
      // 3. 调用了很多次
      // 4. 没有给回调函数传入指定的参数
      callback(req.responseText)
    } else {
      console.log(new Error(req.statusText))
    }
  }
  req.onerror = function () {
    console.log(new Error(req.statusText))
  }
  req.send()
}

function callback(text) {
  console.log(text)
}

let url = 'http://api.myjson.com/bins/16hvos'
getData(url, callback)

2. 使用promise的异步处理

function getData(url) {
  return new Promise(function (resolve, reject) {
    let req = new XMLHttpRequest()
    req.open('GET', url, true)
    req.onload = function () {
      if (req.status === 200) {
        resolve(req.responseText)
      } else {
        reject(new Error(req.statusText))
      }
    }
    req.onerror = function () {
      reject(new Error(req.statusText))
    }
    req.send()
  })
}

let url = 'http://api.myjson.com/bins/16hvos'
getData(url).then(function onFullfilled(value) {
  console.log(value)
}).catch(function onRejected(value) {
  console.log(value)
})

2.2 回调地狱

回调地狱会导致代码晦涩难懂,不易维护,错误处理复杂。

1. 回调地狱(callback hell)示例代码

// 先执行taskA 得到符合预期结果; 执行taskB, 得到预期结果,执行taskC, 得到预期结果,接着处理。。。
function taskA(callback) {
  let result = 1
  setTimeout(() => {
    callback(result)
  }, 2000)
}

function taskB(callback) {
  let result = 2
  setTimeout(() => {
    callback(result)
  }, 2000)
}

function taskC(callback) {
  let result = 3
  setTimeout(() => {
    callback(result)
  }, 2000)
}

taskA(function (res) {
  if (res === 1) {
    taskB(function (res) {
      if (res === 2) {
        taskC(function (res) {
           if (res === 3) {
             console.log(res)
           }
        })
      }
    })
  }
})

可以看出每执行一个函数都需要给其一个回调函数,而且代码末尾有很多括号的嵌套,虽然可以使用一定方法可以改善这种嵌套的方法,但是依然代码依然不够易读。比如采用如下方法抽离一部分代码

function a(res) {
  if (res === 1) {
    taskB(b)
  }
}

function b (res) {
  if (res === 2) {
    taskC(c)
  }
}

function c(res) {
  if (res === 3) {
    console.log(res)
  }
}

taskA(a) // 3

即使采用以上方式处理,阅读此代码仍然让人头大。

2. 使用promise处理回调地狱

// taskA执行,拿到预期结果,返回一个resolved的promise对象
function taskA() {
  let result = 1
  return new Promise(function (resolve, reject) {
    setTimeout(() => {
      if (result === 1) {
        resolve(result)
      } else {
        reject(new Error('fail'))
      }
    }, 2000)
  })
}

// 一个resolved状态的promise对象可以在其then方法中拿到异步处理的结果
// 在then方法中的回调函数中使用return会新生成一个resolved状态的promise对象
taskA().then(function (result) {
  return result
}).then(function taskB() {
  let result = 2
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(result)
    }, 2000)
  })
}).then(function taskC() {
  let result = 3
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(result)
    }, 2000)
  })
}).then(function (result) {
  console.log(result)
})

三. promise的使用

3.1 创建一个promise对象

1. 构造函数形式

Promise函数接受一个函数作为参数,这个函数有两个参数,resolve和reject, 用来改变所生成的promise对象的状态。这个函数参数会立即执行。

let promise = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve(42)
  }, 2000)
})
promise.then(function (value) {
  console.log(value) // 42
})

Object.getPrototypeOf(promise) === Promise.prototype // true

2. Promise.resolve

Promise.resolve() 直接返回一个已解析状态的promise对象。

var promise = Promise.resolve(42)
// 相当于
var promise = new Promise(function (resolve, reject) {
  resolve(42)
})

3. Promise.reject

Promise.reject 直接返回一个已拒绝状态的promise对象。

var promise = Promise.reject(new Error('ok'))
// 相当于
var promise = new Promise(function (resolve, reject) {
  reject(new Error('fail'))
})

3.2 promise#then 和 promise#catch

1. promise#then

(1) 每一个promise对象都有then方法,当这个promise对象的状态变为resolved时,会执行then中注册的回调函数。

let promise = Promise.resolve(42)
promise.then(function (value) {
  console.log(value) //42
})

(2) promise对象的then方法会返回一个新的promise对象,因此我们可以在新生成的promise对象中继续使用then方法。

let promise = Promise.resolve(42)
promise.then(function (value) {
  console.log(value)
}).then(function (value) {
  console.log('ok')
})

根据then方法的特性可以实现promise对象的链式调用。

let promise = Promise.resolve()

function taskA() {
  console.log('taskA')
}

function taskB() {
  console.log('taskB')
}

function onRejected(error) {
  console.log(error)
}

promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
// taskA taskB 

以上例子中,实现了promise的链式调用。当taskA和taskB中没有抛出错误时,程序会一直执行下去,其中catch是为了捕获taskA或taskB出现的异常。如果taskA或者taskB中出现了错误,那么catch中的回调函数就会执行。如下:

let promise = Promise.resolve()

function taskA() {
  throw new Error('fail')
}

function taskB() {
  console.log('taskB')
}

function onRejected(error) {
  console.log(error)
}

promise
  .then(taskA)
  .then(taskB)
  .catch(onRejected)
// Error fail

如果taskA出现了一个错误,那么taskB将不会执行,会执行调用catch中的回调函数。 3). 链式调用传值 想要实现链式调用的传值,只需要在then中的回调函数给出返回值,Promise就会自动将这个返回值解析为一个promise对象。

let promise = Promise.resolve(10)

function taskA(value) {
  return value + 10
}

function taskB(value) {
  return value + 10 
}

function taskC(value) {
  console.log(value)
}

promise
  .then(taskA)
  .then(taskB)
  .then(taskC) // 30

2. promise#catch

当一个promise的状态为rejected时,那么它的catch方法中的回调函数将会被执行。catch方法其实就是then方法中第二个回调函数。

let promise = Promise.reject('fail')
promise.catch(function (error) {
  console.log(error) // fail
})

// 相当于
promise.then(undefined, function (error) {
  console.log(error) // fail
})

catch也会返回一个promise对象。

3.3 Promise.all 和 Promise.race

1. Promise.all

Promise.all接受一个由promise对象组成的数组参数,当每个promise的状态都变成resoved或rejected时,Promise.all返回的promise对象的状态才会改变(resolved或者rejected)。

// getData函数返回一个promise, 并且函数体内的Promise函数会立即执行,即立即执行函数体内的异步任务
function getData(url) {
  return new Promise(function (resolve, reject) {
    var req = new XMLHttpRequest()
    req.open('GET', url, true)
    req.onload = function () {
      if (req.status === 200) {
        return req.responseText
      } else {
        return new Error(req.statusText)
      }
    }
    req.onerror = function () {
      reject(new Error(req.statusText))
    }
    req.send()
  })
}

// 错误写法
// getData函数前加了async关键字,因此它也返回一个promise对象,但是函数体内的请求时异步的,
// getData会立即执行,然后再去执行异步任务,因此getData返回的promise永远是resolved undefined
async function getData(url) {
  var req = new XMLHttpRequest()
  req.send()
  req.open('GET', url, true)
  req.onload = function () {
    if (req.status === 200) {
      console.log('ok')
      return req.responseText
    } else {
      return new Error(req.statusText)
    }
  }
  req.onerror = function () {
    reject(new Error(req.statusText))
  }
}

Promise.all([
  getData('http://azu.github.io/promises-book/json/comment.json'),
  getData('http://azu.github.io/promises-book/json/people.json')
]).then(res => {
  console.log(res)
})

2. Promise.race

Promise.race也接受一个由promise对象组成的数组参数,当其中的一个promise对象的状态变为resolved或是rejected时,Promise.race返回的promise对象才会改变(resolved或者rejected)。

3.4 只执行异步操作的promise 和 async/await

1. 一个立即变为resolved状态的promise对象,promise.then中注册的回调函数还是会被以异步方式调用。
function asyncPromise () {
  return new Promise(resolve => {
    resolve(3)
  })
}

function test() {
  asyncPromise().then(function (value) {
    console.log(value)
  })
  console.log(1)
}

test()
console.log(2)
// 输出顺序为 1 2 3

2. 一个function前加了async关键字,那么这个函数会返回一个promise对象

可以把async当做是一个返回值为promise对象的函数的语法糖。

async function test() {
  return 1
}
test() // Promise {<resolved>: 1} google浏览器的打印结果

// 相当于
function test() {
  return Promise.resolve(1)
}
test() // Promise {<resolved>: 1} google浏览器的打印结果

3. await 关键字后面如果是一个promise对象,那么它会把这个promise对象解析后的结果取出来,并且其后面的不管是同步代码还是异步代码都会等到这个promise对象解析出来后(由pedding变为resolved或rejected),才会执行后面的代码。

await 让异步代码变为了同步(同步执行)。

function asyncFunction () {
  return new Promise(resolve => {
    resolve(2)
  })
}

async function test() {
  let value = await asyncFunction()
  console.log(value)
  console.log(3)
}

test()
console.log(1)
// 打印结果为 1 2 3 

4. 项目实践

采用两种方式模拟一个经过vuex获取数据的过程。 (1) 直接返回promise对象的方式

// src/People.vue
created () {
  this.$store.dispatch('GetPeople', { page: 1, size: 20 }).then(response => {
    console.log(response) // [{ id: 1, name: 'Joe'}]
  })
}

// src/store/people/actions.js
GetPeople({ commit }, payload) {
  return new Promise(function (resolve, reject) {
    axios.get('http://www.example.com/people', { payload })
      .then(function (response) {
         resolve(response) // response = [{ id: 1, name: 'Joe'}]
      }).catch(function (error) {
         reject(error)
      })
  })
}

(2) async / await

// src/People.vue
async created() {
  await this.$store.dispatch('GetPeople', { page: 1, size: 20 })
  // [{ id: 1, name: 'Joe'}]
}

// src/store/people/actions.js
async GetPeople({ commit }, payload) {
  const response = await axios.get('http://www.example.com/people', { payload })
  return response
}

参考:

you don't know js

promise 迷你书

<关于我们>

我们是来自帝都的一枚前端程序猿 + 一枚前端程序媛。

这里发布的文章是我们对学习内容的总结,预计会每周至少会更新一篇。

目前我们学习计划是: 小程序实战 => vue 进阶用法 => vue 原理 => css 基础 => es6 => js 深入

另外,工作中用到的一些技术和完成的功能,我们也会及时总结更新在这里

如文章有错误或表述不清晰,欢迎各位留言反馈~~