JS的Promise兄弟

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS的Promise兄弟相关的知识,希望对你有一定的参考价值。

参考技术A

  相信用过JS的都知道JS是单线程的,同步的函数先执行,异步的函数先加入到一个队列中等同步执行完了再执行异步函数。基于这个JS采用异步回调的方式来处理需要等待的事件,是的代码会继续执行而不用在异步处理的地方一直等待着。同时也带来一个不好的方面,如果我们有很多的回调函数, 也就是说一个回调函数里边再嵌套一个回调一层一层的嵌套,这样就很容易进入传说中的回调地狱。

   注意:异步和回调不是一个东西

  下面感受一下回调地狱代码的魅力:

  是挺有美感的但是阅读性很差,写法也让人感到无力,es6新出的promise对象已经es7的async await都可以解决这个问题,但是今天的主角是Promise。

  Promise是异步编程的一种解决方案,可以替代传统的解决方案--回调函数和事件。ES6统一了用法,并原生提供了Promise对象。作为对象,Promise有一下两个特点:(1)对象的状态不受外界影响;(2)一旦状态改变了就不会在变,也就是说任何时候Promise都只有一种状态。Promise有三种状态,分别是:Pending(进行中),Resolved(完成),Rejected (失败)。Promise从Pending状态开始,如果成功就转到成功态,并执行resolve回调函数;如果失败就转到失败状态并执行reject回调函数。

  Promise一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

  Promise构造函数接收一个函数作为参数,该函数的两个参数是resolve,reject,它们由javascript引擎提供。其中resolve函数的作用是当Promise对象转移到成功,调用resolve并将操作结果作为其参数传递出去;reject函数的作用是单Promise对象的状态变为失败时,将操作报出的错误作为其参数传递出去

  介绍一下Promise的api怎么使用:

   1、Promise.resolve()的作用将现有对象转为Promise对象resolved;Promise.resolve(\'test\')==new Promise(resolve=>resolve(\'test\'))

  2、Promise.reject()返回一个Promise对象,状态为rejected

  3、Promise.prototype.then()方法接受两个参数,第一个是成功的resolved的回调,另一个是失败rejected的回调,第二个失败的回调参数可选。并且then方法里也可以返回promise对象,这样就可以链式调用了。

   4、Promise.prototype.catch()发生错误的回调函数。

   5、Promise.all() // 所有的事都有完成,相当于 且,适合用于所有的结果都完成了才去执行then()成功的操作。

   6、Promise.race() // 完成一个任务即可,相当于 或。(这个经常用在一些图片比较多的网站)

JS 异步系列 —— Promise 札记

Promise

研究 Promise 的动机大体有以下几点:

  • 对其 api 的不熟悉以及对实现机制的好奇;

  • 很多库(比如 fetch)是基于 Promise 封装的,那么要了解这些库的前置条件得先熟悉 Promise;

  • 要了解其它更为高级的异步操作得先熟悉 Promise;

基于这些目的,实践了一个符合 Promise/A+ 规范的 repromise

本札记系列总共三篇文章,作为之前的文章 Node.js 异步异闻录 的拆分和矫正。

Promise/A+ 核心

在实现一个符合 Promise/A+ 规范的 promise 之前,先了解下 Promise/A+ 核心,想更全面地了解可以阅读 Promise/A+规范

  • Promise 操作只会处在 3 种状态的一种:未完成态(pending)、完成态(resolved) 和失败态(rejected);
  • Promise 的状态只会出现从未完成态向完成态或失败态转化;
  • Promise 的状态一旦转化,将不能被更改;

repromise api 食用手册

Promise.resolve()

Promise.resolve() 括号内有 4 种情况

/* 跟 Promise 对象 */
Promise.resolve(Promise.resolve(1))
// Promise?{state: "resolved", data: 1, callbackQueue: Array(0)}

/* 跟 thenable 对象 */
var thenable = {
  then: function(resolve, reject) {
    resolve(1)
  }
}

Promise.resolve(thenable)
// Promise?{state: "resolved", data: 1, callbackQueue: Array(0)}

/* 普通参数 */
Promise.resolve(1)
// Promise?{state: "resolved", data: 1, callbackQueue: Array(0)}

/* 不跟参数 */
Promise.resolve()
// Promise?{state: "resolved", data: undefined, callbackQueue: Array(0)}

Promise.reject()

相较于 Promise.resolve(),Promise.reject() 原封不动地返回参数值

Promise.all(arr)

对于 Promise.all(arr) 来说,在参数数组中所有元素都变为决定态后,然后才返回新的 promise。

// 以下 demo,请求两个 url,当两个异步请求返还结果后,再请求第三个 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)

Promise.all([p1, p2])
  .then((datas) => { // 此处 datas 为调用 p1, p2 后的结果的数组
    return request(`http://some.url.3?a=${datas[0]}&b=${datas[1]}`)
  })
  .then((data) => {
    console.log(msg)
  })

Promise.race(arr)

对于 Promise.race(arr) 来说,只要参数数组有一个元素变为决定态,便返回新的 promise。

// race 译为竞争,同样是请求两个 url,当且仅当一个请求返还结果后,就请求第三个 url
const p1 = request(`http://some.url.1`)
const p2 = request(`http://some.url.2`)

Promise.race([p1, p2])
  .then((data) => { // 此处 data 取调用 p1, p2 后优先返回的结果
    return request(`http://some.url.3?value=${data}`)
  })
  .then((data) => {
    console.log(data)
  })

Promise.wrap(fn) —— 回调函数转 Promise

通过下面这个案例,提供回调函数 Promise 化的思路。

function foo(a, b, cb) {
  ajax(
    `http://some.url?a=${a}&b=${b}`,
    cb
  )
}

foo(1, 2, function(err, data) {
  if (err) {
    console.log(err)
  } else {
    console.log(data)
  }
})

如上是一个传统回调函数使用案例,只要使用 Promise.wrap() 包裹 foo 函数就对其完成了 promise 化,使用如下:

const promiseFoo = Promise.wrap(foo)

promiseFoo(1, 2)
  .then((data) => {
    console.log(data)
  })
  .catch((err) => {
    console.log(err)
  })

Promise.wrap 的实现逻辑也顺带列出来了:

Promise.wrap = function(fn) {
  return funtion() {
    const args = [].slice.call(arguments)
    return new Promise((resolve, reject) => {
      fn.apply(null, args.concat((err, data) => {
        if (err) {
          reject(err)
        } else {
          resolve(data)
        }
      }))
    })
  }
}

then/catch/done

这几个 api 比较简单,合起来一起带过

Promise.resolve(1)
  .then((data) => {console.log(data)}, (err) => {console.log(err)}) // 链式调用,可以传一个参数(推荐),也可以传两个参数
  .catch((err) => {console.log(err)}) // 捕获链式调用中抛出的错误 || 捕获变为失败态的值
  .done()                             // 能捕获前面链式调用的错误(包括 catch 中),可以传两个参数也可不传

实践过程总结

坑点 1:事件循环

事件循环:同步队列执行完后,在指定时间后再执行异步队列的内容。

之所以要单列事件循环,因为代码的执行顺序与其息息相关,此处用 setTimeout 来模拟事件循环;

下面代码片段中,① 处执行完并不会马上执行 setTimeout() 中的代码(③),而是此时有多少次 then 的调用,就会重新进入 ② 处多少次后,再进入 ③

excuteAsyncCallback(callback, value) {
  const that = this
  setTimeout(function() {
    const res = callback(value) // ③
    that.excuteCallback('fulfilled', res)
  }, 4)
}

then(onResolved, onRejected) {
  const promise = new this.constructor()
  if (this.state !== 'PENDING') {
    const callback = this.state === 'fulfilled' ? onResolved : onRejected
    this.excuteAsyncCallback.call(promise, callback, this.data)              // ①
  } else {
    this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // ②
  }
  return promise
}

坑点 2:this 的指向问题

this.callbackArr.push() 中的 this 指向的是 ‘上一个’ promise,所以类 CallbackItem 中,this.promise 存储的是‘下一个‘ promise(then 对象)。

class Promise {
  ...
  then(onResolved, onRejected) {
    const promise = new this.constructor()
    if (this.state !== 'PENDING') {        // 第一次进入 then,状态是 RESOLVED 或者是 REJECTED
      const callback = this.state === 'fulfilled' ? onResolved : onRejected
      this.excuteAsyncCallback.call(promise, callback, this.data)  // 绑定 this 到 promise
    } else {                               // 从第二次开始以后,进入 then,状态是 PENDING
      this.callbackArr.push(new CallbackItem(promise, onResolved, onRejected)) // 这里的 this 也是指向‘上一个’ promise
    }
    return promise
  }
  ...
}

class CallbackItem {
  constructor(promise, onResolve, onReject) {
    this.promise = promise // 相应地,这里存储的 promise 是来自下一个 then 的
    this.onResolve = typeof(onResolve) === 'function' ? onResolve : (resolve) => {}
    this.onReject = typeof(onRejected) === 'function' ? onRejected : (rejected) => {}
  }
  ...
}

more

实践的更多过程可以参考测试用例。有好的意见欢迎交流。

以上是关于JS的Promise兄弟的主要内容,如果未能解决你的问题,请参考以下文章

js的promise如何递归调用?

js-Promise基础

js中promise到底怎么用

JS ~ Promise 对象

JS ~ Promise.reject()

JS中promise对象的作用与使用