Promise详解

65 阅读4分钟

什么是Promise?

我们使用console.dir(Promise)来看一下 Promise是什么.png         Promise本身是一个构造函数,静态方法有all、allsettled、any、race、reject、resolve,当我们通过const promise = new Promise()创建一个Promise对象时,promise可以调用then、catch、finally方法。
        ES6中提出,Promise是一种异步编程的解决方案,比起传统的解决方案——回调函数和事件,它更加的强大以及合理。
        简单来说,Promise就是一个容器,里面保存着某个未来才会结束的结果。

Promise的简单用法

        Promise是一个规范,它尝试用更加有好的方式来书写代码。Promise对象接收一个函数作为参数,函数提供两个参数:

  • resolve:将promise从未完成切换到成功状态,也就是上面提到的从pending切换到fufilled,resolve可以传递参数,下一级promise中的成功函数会接收到它;
  • reject:将promise从未完成切换到失败状态,即从pending切换到rejected
function runSync() {
  const promise = new Promise(function (resolve, reject) {
    const num = Math.ceil(Math.random() * 10);
    if (num % 2 == 0) {
      resolve("结果为偶数");
    } else {
      reject("结果为奇数");
    }
  })
  return promise;
}
runSync().then(
  function (data) {
    console.log(data);
  },
  function (err) {
    console.log(err);
  }
)

        这里需要注意的是,一旦Promise的状态发生改变,就表示Promise已完成决议(状态不会再更改)。且Promise只能被决议一次,如果你多次决议,她只会执行第一次决议。

const promise = new Promise(function (resolve, reject) {
  resolve(1);
  setTimeout(function () {
    resolve(2);
  }, 1000);
  resolve(3);
})
promise.then(function (val) {
  console.log(val);  // 1
})

为什么会出现Promise?

        Promise提供了对异步编程新的解决方案,传统的回调函数存在很大的问题,受制于js的单线程等原因导致不得不重复书写。当然,Promise并没有摆脱回调,只是改变了回调的位置。

异步编程——回调函数

        使用传统的回调函数来解决异步问题时,可能会产生大量的嵌套导致代码难以阅读和修改(不优雅),也就是我们说的回调地狱。

$.ajax("url1", function response(res1) {
  $.ajax("url2", function response(res2) {
    $.ajax("url3", function response(res3) {
      //...
    })
  })
})
异步编程——Promsie

        Promise的实例具有then方法,它会返回一个新的Promise实例,因此我们可以像下面这样采用链式调用的方式来解决上面的问题;

function task1() {
  $.ajax("url1", function response(res1) {
    //...
  })
}
function task2() {
  $.ajax("url2", function response(res2) {
    //...
  })
}
function task3() {
  $.ajax("url3", function response(res3) {
    //...
  })
}

var promise = new Promise((resolve, reject) => { resolve() });
promise.then(task1)
  .then(task2)
  .then(task3);

Promise的静态方法(all、race、allSettled)

Promise.all()

        Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。Promise.all()方法接受一个数组作为参数,p1p2都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
        p的状态由p1p2决定,分成两种情况:

  • 只有p1p2的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2的返回值组成一个数组,传递给p的回调函数。
const p1 = new Promise(function (resolve, reject) {
  resolve(111)
})
const p2 = new Promise(function (resolve, reject) {
  resolve(222)
})

const p = Promise.all([p1, p2])
  .then(function (data) {
    console.log(data)  // [111, 222]
  })
  • 只要p1p2之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,且不再继续执行,会传递给p的回调函数。
const p1 = new Promise(function (resolve, reject) {
  reject(111)
})
const p2 = new Promise(function (resolve, reject) {
  resolve(222)
})

const p = Promise.all([p1, p2])
  .then(function (data) {
    console.log(data) // Uncaught (in promise) 111
  })
Promise.race()

        Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
        race存在竞速的意思,只要p1p2之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve(111)
  }, 2000)
})
const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => {
    resolve(222)
  }, 1000)
})

const p = Promise.race([p1, p2])
  .then(function (data) {
    console.log(data) // 222
  })
Promise.allSettled()

        Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束,这就是跟Promise.all()的区别。

const p1 = new Promise(function (resolve, reject) {
  reject(111)
})
const p2 = new Promise(function (resolve, reject) {
  resolve(222)
})

const p = Promise.allSettled([p1, p2])
  .then(function (data) {
    console.log(data)
  })
// [
//   { reason: 111, status: "rejected" },
//   { status: "fulfilled", value: 222 }
// ]

        在某些业务场景下我们不关心异步操作的结果,只关心这些操作有没有结束时,使用Promise.allSettled()就很方便。

面试题

使用Pormise封装一下原生Ajax
function ajaxExample(method, url, data, async, timeout) {
  const xhr = new XMLHttpRequest();
  return new Promise(function(resolve, reject) {
    xhr.open(method, url, async);
    xhr.timeout = timeout;
    xhr.onloadend = function() {
      if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        resolve(xhr);
      } else {
        reject({
          errorType: "status_error",
          xhr: xhr,
        })
      }
    };
    xhr.send(data);
    // 错误处理
    xhr.onabort = function() {
      reject(new Error({
        errorType: "abort_error",
        xhr: xhr,
      }));
    };
    xhr.ontimeout = function() {
      reject({
        errorType: "timeout_error",
        xhr: xhr,
      });
    };
    xhr.onerror = function() {
      reject({
        errorType: "onerror",
        xhr: xhr,
      });
    };
  })
}
封装一个Promise.all()方法
function ajaxExample(array) {
  let num = 0;
  const result = [];
  return new Promise(
    function (resolve, reject) {
      array.forEach(promise => {
        promise.then(
          function (val) {
            result.push(val);
            num++;
            if (num >= array.length) {
              resolve(result);
            }
          },
          function (err) {
            reject(err);
          }
        )
      })
    }
  )
}