Javascript 异步编程(五)

Promise

Promise 承诺,在异步编程中用来描述异步任务,当前不执行,承诺执行,美剧里经常是I
promise~ 挂在嘴边

ES6 Promise Api

  • new Promise(..) 构造器

    var p = new Promise( function(resolve,reject){
    // 接收函数作为构造器参数   
    // resolve(..)用于将promise状态从pending改为fulfilled
    // reject(..)用于将promise状态从pending改为rejected
    } );

    resolve(..) 既可能完成 promise,也可能拒绝,要根据传入参数而定。

    如果传给 resolve(..) 的是一个非 Promise、非 thenable 的立即值,这个 promise 就会用这个值完成。

如果传给 resolve(..) 的是一个真正的 Promise 或 thenable 值,这个值就会被递归展开,并且(要构造的)promise 将取用其最终决议值或状态。

  • Promise.resolve 创建一个已完成的 Promise

    语法上两者是等同的

    var p1 = new Promise( function(resolve,reject){
     resolve( "Oops" );
    } );
    var p2 = Promise.resolve( "Oops" );
  • Promise.reject 创建一个已被拒绝的 Promise

    var p1 = new Promise( function(resolve,reject){
     reject( "Oops" );
    } );
    var p2 = Promise.reject( "Oops" );
  • Promise.all

    输入一组promise,返回一个新的promise。只有传入的全部 promise 的状态都变为fulfilled,返回 promise 才能改变为fulfilled。可以理解为逻辑与(&&

  • Promise.allSettled

    输入一组promise,返回一个新的promise。只有传入的所有 promise 的状态都改变后(fulfilledrejected),返回 promise 才能改变为fulfilled

  • Promise.race

    输入一组promise,返回一个新的promise。结果的promise与第一个 状态发生改变(fulfilledrejected)promise相同

  • Promise.any

    接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。类似于逻辑或 (||)

若向 Promise.all([ .. ]) 传入空数组,它会立即完成,但 Promise.race([ .. ]) 会挂住,且永远不会完成。

实例方法:

  • then()

    then(..) 接受一个或两个参数:第一个用于完成回调,第二个用于拒绝回调。如果两者中
    的任何一个被省略或者作为非函数值传入的话,就会替换为相应的默认回调。默认完成回
    调只是把消息传递下去,而默认拒绝回调则只是重新抛出(传播)其接收到的出错原因

  • catch()

    只接受一个拒绝回调作为参数,并自动替换默认完成回调。换句话说,它等价于 then(null,..):

  • finally()

    接收一个参数,不论promise的最终状态如何都会执行,

then(..) 和 catch(..) 也会创建并返回一个新的 promise,这个 promise 可以用于实现
Promise 链式流程控制。

如果完成或拒绝回调中抛出异常,返回的 promise 是被拒绝的。

如果任意一个回调返回非 Promise、非 thenable 的立即值,这个值会被用作返回 promise 的完成值。

如果完成处理函数返回一个 promise 或 thenable,那么这个值会被展开,并作为返回promise 的决议值。

异常控制

对于错误处理,一般我们使用try…catch,但是它只能捕捉同步代码的异常。

try{
  setTimeout(function () {
    console.log('happen',x)
  })
}catch (e) {
  console.log('exception',e)
}
// Uncaught ReferenceError: x is not defined

在promise中,如果发生异常,将由then()中的reject处理函数进行错误捕获。

Promise.reject(10).then((val)=>{
  console.log(val)
},(err)=>{
  console.log('then',err)
})
// then 10
Promise.resolve(10).then(function (val) {
  console.log(val)
  return Promise.reject(val)
}).then(val => {
  console.log(val)
}, err => {
  console.log('then', err)
})
// 10
// then 10

如果忘记在then()中对异常进行捕获,将会丢失被忽略和抛弃的Promise错误。于是,实现Promise异常控制的最佳实践就是以catch()结尾来进行异常处理。

Promise.resolve(10).then(function (val) {
  console.log(val)
  return Promise.reject(val)
}).then(val => {
  console.log(val)
}).catch(err=>{
  console.log('err',err)
})
// err 10

但是,如果我们在catch中发生了异常呢?

Promise.reject(10).catch(err=>{
  console.log('catch',10+x)
})
// ReferenceError: x is not defined
Promise.reject(10).catch(err => {
  console.log('catch', 10 + x)
}).catch(err=>{
  console.log('catch2',err)
})
// catch2 ReferenceError: x is not defined

其实我们可以在调用链后面再次补充一个catch来保证捕获任何可能出现的错误。但是这样的写法不是很优雅,有种常用的方法是使用done(标志着promise的结尾,不会创建和返回promise,不在es6规范中),现阶段可以通过增加 polyfill

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected)
.catch(function (err) {
    // 抛出一个全局错误
    setTimeout(() => { throw err }, 0);
});
};

在浏览器中,我们可以使用 unhandledrejection 事件来捕获这类 error:

window.addEventListener('unhandledrejection', function(event) {
  // 这个事件对象有两个特殊的属性:
  alert(event.promise); // [object Promise] - 生成该全局 error 的 promise
  alert(event.reason); // Error: Whoops! - 未处理的 error 对象
});
new Promise(function() {
  throw new Error("Whoops!");
}); 

如果出现了一个 error,并且在这儿没有 .catch,那么 unhandledrejection 处理程序(handler)就会被触发,并获取具有 error 相关信息的 event 对象,所以我们就能做一些后续处理了

Promise 实践

以下代码的执行顺序是?

const promise=new Promise((resolve,reject)=>{
    console.log(1);
    resolve();
    console.log(2);
})
promise.then(()=>{
    console.log(3);
})
console.log(4);
// 1
// 2
// 4
// 3
  1. 构造器new Promise是同步执行的,所以先打印1,2
  2. then的回调属于微任务,异步执行
  3. 同步执行console.log(4),接着打印 4
  4. 开始执行微任务队列,console.log(3),打印出3

第二段代码

const promise=Promise.resolve(1).then(2).then(Promise.resolve(3)).then(console.log);//1

过程分解

  1. Promise.resolve(1) 生成的promise ,返回值为1,状态为fulfilled
  2. then(2)中,传入了非函数,返回的promise 的值为上一步的promise的值(即为1),状态为fulfilled
  3. then(Promise.resolve(3)) 这一步中参数类型为promise,仍取上一步的promise的值(即为1),状态为fulfilled
  4. then(console.log), 这一步参数为函数,传入值1

最佳实践

  1. 不要忘记catch捕获
  2. then方法中使用return
  3. 传递函数给then方法
  4. 不要把promise写成嵌套

手动实现Promise

class Promise {
  constructor (executor) {
    // 参数校检
    if (typeof executor !== 'function') {
      throw new TypeError(`Promise resolver ${executor} is not a function`)
    }
    this.initValue()
    this.initBind()
    try {
      executor(this.resolve, this.reject)
    } catch (e) {
      this.reject(e)
    }

  }

  initValue () {
    // state:pending/fulfilled/rejected
    this.value = null//终值
    this.reason = null//拒因
    this.state = Promise.PENDING
    //用于处理异步
    this.onFulfilledCallbacks = [] //成功回调
    this.onRejectedCallbacks = []//失败回调
  }

  resolve (value) {
    //成功后的一系列操作(状态的改变,成功回调的执行)
    //状态不可逆 从pending开始
    if (this.state === Promise.PENDING) {
      this.state = Promise.FULFILLED
      this.value = value
      this.onFulfilledCallbacks.forEach(fn => fn(this.value))
    }
  }

  //绑定this
  initBind () {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
  }

  reject (reason) {
    //失败后的一系列操作(状态的改变,失败回调的执行)
    if (this.state === Promise.PENDING) {
      this.state = Promise.REJECTED
      this.reason = reason
      this.onRejectedCallbacks.forEach(fn => fn(this.reason))
    }
  }

  catch (onRejected) {
    return this.then(null, onRejected) // 相当于then里的成功回调只传个null
  }

  then (onFulfilled, onRejected) {
    //实现链式调用且改变了后面then方法的值,必须通过新的实例
    let promise2 = new Promise((resolve, reject) => {
      // 参数校检
      if (typeof onFulfilled !== 'function') {
        onFulfilled = function (value) {
          return value
        }
      }
      if (typeof onRejected !== 'function') {
        onRejected = function (reason) {
          throw reason
        }
      }

      if (this.state === Promise.FULFILLED) {
        //模拟异步任务
        setTimeout(() => {
          try {
            const x = onFulfilled(this.value)
            Promise.resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

      }
      if (this.state === Promise.REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected(this.reason)
            Promise.resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })

      }
      // 处理异步
      if (this.state === Promise.PENDING) {
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              const x = onFulfilled(value)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
        this.onRejectedCallbacks.push(reason => {
          setTimeout(() => {
            try {
              const x = onRejected(reason)
              Promise.resolvePromise(promise2, x, resolve, reject)
            } catch (e) {
              reject(e)
            }
          })
        })
      }
    })
    return promise2
  }
}

Promise.resolvePromise = function (promise, x, resolve, reject) {
  // x与promise相等时 循环引用
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 函数调用一次的开关项 防止重复调用
  let called
  if (x instanceof Promise) {
    /**
     * x为 Promise
     * 1. x 为 pending,promise需保持 pending 直至x被拒绝或执行
     * 2. x 为 fulfilled 用相同的值执行promise
     * 3. x 为 rejected 用相同的拒因执行promise
     */
    x.then(value => {Promise.resolvePromise(promise, value, resolve, reject)},
      reason => {reject(reason)})
  } else if (x && (typeof x === 'object' || typeof x === 'function')) {
    // x 为对象或函数
    try {
      let then = x.then
      // 处理thenable
      if (typeof then === 'function') {
        then.call(x,
          value => {
            if (called) return
            called = true
            Promise.resolvePromise(promise, value, resolve, reject)
          },
          reason => {
            if (called) return
            called = true
            reject(reason)
          })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (called) return
      called = true
      reject(e)
    }
  } else {
    resolve(x)
  }
}
Promise.resolve = function (val) {
  return new Promise(resolve => {
    resolve(val)
  })
}
Promise.reject = function (reason) {
  return new Promise((resolve, reject) => {
    reject(reason)
  })
}
Promise.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(data => {
        resolve(data)
      })
    }
  })
}
Promise.all = function (promises) {
  let result = []
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(value => {
        result.push(value)
        if (result.length === promises.length) {
          resolve(arr)
        }
      }, reject)
    }
  })
}
Promise.PENDING = 'pending'
Promise.FULFILLED = 'fulfilled'
Promise.REJECTED = 'rejected'
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
module.exports = Promise

Reference

Promises/A+ 规范

中文翻译

BAT前端经典面试问题:史上最最最详细的手写Promise教程


王大山
28 声望2 粉丝