什么是Promise

175 阅读5分钟

Promise

什么是Promise

JavaScript是单线程的语言, 通过维护执行栈与任务队列来实现异步操作, setTimeoutAjax就是典型的异步操作

setTimeout((res) => { console.log(res) }, 1000)

但是当多个使用回调函数所实现的异步函数一个接着一个地执行时, 就会产生回调地狱, 此时的代码会变得难以理解, 这是由回调函数的嵌套而引起的 Promise就是异步编程的一种解决方案, 它最早由社区提供, ES6将其写进了语言标准, 统一了用法, 原生提供了Promise对象

Promise对象的两个特点

  1. Promise对象的状态不受外界影响. Promise对象代表一个异步操作, 有三种状态: pending(进行中), fulfilled(已成功), rejected(已失败). 只有异步操作的结果可以决定当前是哪一种状态, 任何其他操作都无法改变这个状态
  2. Promise对象的状态一旦改变就不再变了, 任何时候都可以得到这个结果. Promise对象的状态改变, 只有两种可能: 从pending变为fulfilled和从pending变为rejected(已失败). 只要这两种情况发生, 状态就凝固了, 不会再变了, 会一直保持这个解结果, 这时就称为resolved(已定型). 如果改变已经发生了, 再对Promise对象添加回调函数, 页会立即得到这个结果

Promise也有一些缺点: 首先, 无法取消Promise, 一旦新建它就会立即执行, 无法中途取消; 其次, 如果不设置回调函数, Promise内部抛出的错误不会反应到外部, 如果不主动catch捕获Promise内部抛出的异常, 则在FireFox中异常不会抛出, 在Chrome中的异常会抛出但不会触发window.onerror事件; 当处于pending状态时, 无法得知目前进展到哪一个阶段

有了Promise对象, 就可以将异步操作以同步操作的流程表达出来, 避免了层层嵌套的回调函数. 此外Promise对象提供统一的接口, 使得控制异步操作更加容易

基本用法

Promise对象是一个构造函数, 用来生成Promise实例

const promise = new promise((resole, reject) => {
  ...
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise构造函数接受一个函数作为参数, 该函数的两个参数分别是resolvereject. 它们是两个函数: resolve函数的作用是将Promise对象的状态从"未完成"变为”完成"(即从pending变为resolved), 在异步操作成功时调用, 并将异步操作的结果作为参数传递出去; reject函数的作用是将Promise对象的状态从"未完成"变为"失败"(即从pending变为rejected), 在异步操作失败时调用, 并将异步操作报出的错误, 作为参数传递出去

Promise实例生成以后, 可以用then方法分别指定resolved状态和rejected状态的回调函数

promise.then((value) => {
  // success
}, (error) => {
  // failure
});

then方法可以接收两个回调函数作为参数, 第一个回调函数在当Promise对象的状态变为resolved时调用, 第二个回调函数在当Promise对象的状态变为rejected时调用. 这两个函数都是可选的, 它们都接收Promise对象传出的值作为参数 例子

let promise = new Promise((resolve, reject) => {
  let rand = Math.rnadom() * 2;
  setTimeout(() = > {
    if (rand < 1) resolve(rand);
    else reject(rnad);
  }, 1000)
});
promise.then((rand) => {
  console.log("resolve", rand);		// 接收rand参数, resolve回调执行
}).catch(() => {
  console.log("reject", rand);		// 接收rand参数, reject回调执行
})

Promise新建后会立即执行, 会首先执行创建的在new Promise对象中的语句, 然后then方法指定的回调函数, 将在当前脚本所有同步任务执行完才会执行

let promise = new Promise((resolve, reject) => {
  console.log("Promise");
  resolve();
});

promise.then(() => {
  console.log("resolved");
});

conlose.log("Hi!");
// Promise
// Hi!
// resolved

如果调用resolve函数和reject函数时带有参数, 那么它们的参数会被传递给回调函数. reject函数的参数通常是Error对象是实例, 表示抛出的错误; resolve函数的参数除了正常的值以外, 还可能是另一个Promise实例

const p1 = new Promise((resolve, reject) => {
  // ...
});
const p2 = new Promise((resolve, reject) => {
  // ...
  resolve(p1);		// p1的状态传递给了p2, 即p1的状态决定了P2的状态
       // 如果p1的状态是pending, 那么p2的回调函数就会等待p1的状态改变
  // 如果p1的状态已经是resolved或者rejected, 那么p2的回调函数就会执行
});
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error("fail")), 3000);
});
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => resolve(p1), 1000);
});

p2.then(result => console.log(result))
  . catch(error => console.log(error))

p1和p2都是Promise,由于 p2接收了p1的状态, 所以p2本身的状态无效了, 由p1的状态决定p2的状态, 所以后面的then语句都变成针对p1

Promise.prototype.then()

Promise实例具有then方法, 也就是说then方法是定义在原型对象Promise.prototype上的. 它的作用是为Promise实例添加状态改变时的回调函数 then方法返回的是一个新的Promise实例(不是原来的那个Promise实例)

getJson("/post/1.json").then(
  post => getJSON(post.commentURL)
).then(
  comments => console.log("resolved: ", comments),
  err => console.log("rejected: ", err);
);

Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejecion)的别名, 用于指定发生错误时的回调函数

getJSON('/posts.json').then((posts) => {
  // ...
}).catch((error) => {
  // 处理getJSON和前一个回调函数运行时发生的错误
  console.log("发生错误", error);
});

如果Promise状态已经变成resolved, 再抛出错误是无效的

const promise = new Promise((resolve, reject) => {
  resolve("ok");
  throw new Error("test");
});

Promise对象的错误具有"冒泡"性质, 会一直向后传递, 直到被捕获为止. 也就是说, 错误总是会被下一个catch语句捕获

getJSON("/post/1.json").then((post) => {
  return getJSON(post.commentURL);
}).then((comments) => {
  // some code
}).catch((error) => {
  // 处理前面三个Promise产生的错误
})

Promise实现Ajax

let ajax = (method, url, data) => {
  let request = new XMLHttpRequest();
  return new Promise((resolve, reject) => {
    request.onreadystatechange = () => {
      if (request.readyState === 4) {
        if (request.status === 200) {
          resolve(request.responseText);
        } else {
          reject(request.status);
        }
      }
    };
    request.open(method, url);
    request.send(data);
  });
}

ajax("GET", "https://www.google.com", []).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});