天天看点

Promise

Callback 异步操作

es5中处理异步的时候,容易出现回调地狱的现象如

function loadScript (src, callback) {

  let script = document.createElement('script')

  script.src = src

  script.onload = () => callback(script)

  document.head.append(script)

}

loadScript('1.js', function (error, script) {

  if (error) {

    handleError(error)

  } else {

    // ...

    loadScript('2.js', function (error, script) {

      if (error) {

        handleError(error)

      } else {

        // ...

        loadScript('3.js', function (error, script) {

          if (error) {

            handleError(error)

          } else {

            // ...加载所有脚本后继续 (*)

          }

        })

      }

    })

  }

})

如果嵌套变多,代码层次就会变深,维护难度也随之增加,尤其是如果我们有一个不是 … 的真实代码,就会包含更多的循环,条件语句等。

这有时称为 “回调地狱” 或者“回调金字塔”。

Promise

将上面用callback来处理的问题转为用promise来处理

function loadScript (src) {

  return new Promise((resolve, reject) => {

    let script = document.createElement('script')

    script.src = src

    script.onload = () => resolve(script)

    script.onerror = (err) => reject(err)

    document.head.append(script)

  })

loadScript('1.js')

  .then(loadScript('2.js'), (err) => {

    console.log(err)

  .then(loadScript('3.js'), (err) => {

  • 基本语法

new Promise( function(resolve, reject) {…} );

通过创建 Promise 对象开启一个异步操作的过程,一般用几步完成多次异步操作:

  1. new Promise(fn) 返回一个Promise 对象
  2. 在fn 中指定异步等处理
  3. 处理结果正常的话,调用resolve(处理结果值)
  4. 处理结果错误的话,调用reject(Error对象)
  • 场景--封装一个ajax方法

function getURL (URL) {

  return new Promise(function (resolve, reject) {

    var req = new XMLHttpRequest()

    req.open('GET', URL, true)

    req.onload = function () {

      if (req.status === 200) {

        resolve(req.responseText)

        reject(new Error(req.statusText))

    }

    req.onerror = function () {

      reject(new Error(req.statusText))

    req.send()

// 运行示例

var URL = 'http://httpbin.org/get'

getURL(URL).then(function onFulfilled (value) {

  console.log(value)

}).catch(function onRejected (error) {

  console.error(error)

Promise 内部是有状态的(pending、fulfilled、rejected),Promise 对象根据状态来确定执行哪个方法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是完成的,状态会被修改为 fulfilled,如果异步操作遇到异常,状态会被修改为 rejected,

状态转化是单向的,不可逆转,已经确定的状态(fulfilled/rejected)无法转回初始状态(pending),而且只能是从 pending 到 fulfilled 或者 rejected

then方法

.then()是一种链式调用的方法

.then是promise对象原型上的方法

promise.then(onFulfilled, onRejected);

例:

var promise = new Promise(function (resolve, reject) {

  resolve('传递给then的值')

promise.then(function (value) {

}, function (error) {

这段代码创建一个 Promise 对象,定义了处理 onFulfilled 和 onRejected 的函数(handler),然后返回这个 Promise 对象。

这个 Promise 对象会在变为 resolve 或者 reject 的时候分别调用相应注册的回调函数。

  • 当 handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
  • 定义的 handler 中产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法。
回顾

回顾这段代码,因为promise(onFulfilled,onRejected),它的onFulfilled和onRejected是函数,当不是函数的时候,会被忽略,返回一个空的promise对象.

但是如上例子中,为什么被忽略掉还能执行呢?

因为其中的loadScript(2.js)它会被当成是一个表达式,来求取其值,求取结果的时候,它就会自然地被执行,从而返回一个promise对象.

既然应该是函数,那么该例应该写成以下写法,不然则会埋坑

  .then(()=> { 

    return loadScript('2.js')

  }, (err) => {

    return loadScript('3.js')

极简promise雏形

function Promise(fn) {

    var value = null,

        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调

    this.then = function (onFulfilled) {

        callbacks.push(onFulfilled);

    };

    function resolve(value) {

        callbacks.forEach(function (callback) {

            callback(value);

        });

    }

    fn(resolve);

大致逻辑是这样的:

  1. 调用then方法,将想要在Promise异步操作成功时执行的回调放入callbacks队列,其实也就是注册回调函数,可以向观察者模式方向思考;
  2. 创建Promise实例时传入的函数会被赋予一个函数类型的参数,即resolve,它接收一个参数value,代表异步操作返回的结果,当一步操作执行成功后,用户会调用resolve方法,这时候其实真正执行的操作是将callbacks队列中的回调一一执行;

Resolve & Reject 异步操作

一般情况下我们都会使用 new Promise() 来创建 Promise 对象,但是除此之外我们也可以使用其他方法。

静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。

比如 Promise.resolve(42) 可以认为是以下代码的语法糖。

new Promise(function (resolve) {

  resolve(42)

在这段代码中的 resolve(42) 会让这个 Promise 对象立即进入确定(即resolved)状态,并将 42 传递给后面 then 里所指定的 onFulfilled 函数。

方法 Promise.resolve(value) 的返回值也是一个 Promise 对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。

Promise.resolve(42).then(function (value) {

Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。

比如 Promise.reject(new Error(“出错了”)) 就是下面代码的语法糖形式。

new Promise(function (resolve, reject) {

  reject(new Error('出错了'))

这段代码的功能是调用该Promise 对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。

Catch 异步操作

捕获异常是程序质量保障最基本的要求,可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常。

p.catch(onRejected);

p.catch(function(reason) {

// rejection

});

以之前then的处理代码来看,对它进行修改,使用catch来处理出错

  .catch(err => {

建议用 reject(new Error())的方式来触发错误,而不是使用throw new Error来触发

因为 throw 的方式并没有改变 Pronise 的状态

All 异步操作

Promise.all(promiseArray);

Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法。参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。

如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。

由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Promise.all 可以处理不同类型的 promose对象。

示例:

var p1 = Promise.resolve(1)

var p2 = Promise.resolve(2)

var p3 = Promise.resolve(3)

Promise.all([p1, p2, p3]).then((results) => {

  console.log(results) // [1, 2, 3]

Race 异步操作

Promise.race 生成并返回一个新的 Promise 对象.

参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话, 该函数就会返回,并使用这个 Promise 对象的值进行 resolve 或者 reject。

Promise.race([p1, p2, p3]).then((value) => {

  console.log(value) // 1

参考资料

更多