网站

JavaScript Promise

使用 Promise - JavaScript | MDN

Promise.all() | MDN

引入

回调函数是最早的异步处理方式,但嵌套多了就会变成回调地狱。

Promise 是 ES6 引入的异步解决方案,它把异步操作的结果用一个对象来表示。不管操作成功还是失败,都能用统一的方式处理。

Promise 让异步代码从嵌套变成链式调用,读起来更清楚。

正文

定义

定义

Promise 是一个代表异步操作最终结果的对象。它有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

三种状态

状态含义说明
pending进行中初始状态,还没出结果
fulfilled已成功操作完成,拿到了结果
rejected已失败操作失败,拿到了错误

状态变化只有两种可能:

  • pendingfulfilled
  • pendingrejected

状态一旦改变,就不会再变。

基本语法

const promise = new Promise(function (resolve, reject) {
  let success = true;
 
  if (success) {
    resolve("操作成功");
  } else {
    reject("操作失败");
  }
});
  • new Promise() 创建一个新的 Promise
  • resolve 是成功时调用的函数,传入结果
  • reject 是失败时调用的函数,传入错误信息

.then()

.then() 用来处理成功的结果。

const promise = new Promise(function (resolve, reject) {
  resolve("数据加载完成");
});
 
promise.then(function (result) {
  console.log(result);
});

输出:数据加载完成

result 就是 resolve() 传入的值。

.catch()

.catch() 用来处理失败的情况。

const promise = new Promise(function (resolve, reject) {
  reject("网络错误");
});
 
promise.catch(function (error) {
  console.log(error);
});

输出:网络错误

error 就是 reject() 传入的值。

.finally()

.finally() 无论成功还是失败都会执行。

const promise = new Promise(function (resolve, reject) {
  resolve("完成");
});
 
promise
  .then(function (result) {
    console.log(result);
  })
  .finally(function () {
    console.log("收尾工作");
  });

输出:

完成
收尾工作

.finally() 常用于关闭加载动画、恢复按钮状态等收尾操作。

链式调用

Promise 最强大的地方是链式调用。

new Promise(function (resolve, reject) {
  resolve(1);
})
  .then(function (value) {
    console.log(value);
    return value + 1;
  })
  .then(function (value) {
    console.log(value);
    return value + 1;
  })
  .then(function (value) {
    console.log(value);
  })
  .catch(function (error) {
    console.log(error);
  });

输出:

1
2
3

执行流程:

  1. resolve(1) → 第一个 .then() 接收到 1
  2. return value + 1 → 第二个 .then() 接收到 2
  3. return value + 1 → 第三个 .then() 接收到 3
  4. 没有错误,.catch() 不执行

每个 .then() 的返回值会传给下一个 .then()

链式调用中的错误处理

new Promise(function (resolve, reject) {
  resolve(1);
})
  .then(function (value) {
    console.log(value);
    throw new Error("出错了");
  })
  .then(function (value) {
    console.log("不会执行");
  })
  .catch(function (error) {
    console.log(error.message);
  });

输出:

1
出错了

链式调用中任何一步抛出错误,都会跳到 .catch(),后面的 .then() 不再执行。

用 Promise 封装异步操作

把回调风格的代码改成 Promise:

function wait(seconds) {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve(seconds + " 秒后执行完成");
    }, seconds * 1000);
  });
}
 
wait(2).then(function (message) {
  console.log(message);
});

封装网络请求:

function fetchData(url) {
  return new Promise(function (resolve, reject) {
    fetch(url)
      .then(function (response) {
        return response.json();
      })
      .then(function (data) {
        resolve(data);
      })
      .catch(function (error) {
        reject(error);
      });
  });
}
 
fetchData("/api/users")
  .then(function (data) {
    console.log(data);
  })
  .catch(function (error) {
    console.log("请求失败:" + error.message);
  });

改造回调地狱

之前的回调地狱:

getUser(function (user) {
  getOrders(user, function (orders) {
    getOrderDetail(orders[0], function (detail) {
      console.log(detail);
    });
  });
});

改成 Promise 链式调用:

getUser()
  .then(function (user) {
    return getOrders(user);
  })
  .then(function (orders) {
    return getOrderDetail(orders[0]);
  })
  .then(function (detail) {
    console.log(detail);
  })
  .catch(function (error) {
    console.log("出错:" + error.message);
  });

代码变成从上往下平铺,结构清楚很多。

Promise.all()

Promise.all() 同时执行多个 Promise,全部成功才返回结果。

const p1 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve("结果 1");
  }, 1000);
});
 
const p2 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve("结果 2");
  }, 2000);
});
 
Promise.all([p1, p2]).then(function (results) {
  console.log(results);
});

输出(2 秒后):["结果 1", "结果 2"]

如果其中任何一个失败,整个 Promise.all() 就会进入 .catch()

Promise.race()

Promise.race() 同时执行多个 Promise,最先完成的那个决定结果。

const p1 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve("p1 赢了");
  }, 1000);
});
 
const p2 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve("p2 赢了");
  }, 3000);
});
 
Promise.race([p1, p2]).then(function (result) {
  console.log(result);
});

输出(1 秒后):p1 赢了

常见用法:给请求设置超时。

const request = fetch("/api/data");
 
const timeout = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject("请求超时");
  }, 5000);
});
 
Promise.race([request, timeout])
  .then(function (response) {
    console.log("请求成功");
  })
  .catch(function (error) {
    console.log(error);
  });

Promise.any()

Promise.any() 同时执行多个 Promise,只要有一个成功就返回结果。

const p1 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject("p1 失败");
  }, 1000);
});
 
const p2 = new Promise(function (resolve) {
  setTimeout(function () {
    resolve("p2 成功");
  }, 2000);
});
 
Promise.any([p1, p2]).then(function (result) {
  console.log(result);
});

输出(2 秒后):p2 成功

只有全部失败才会进入 .catch()

对比三个方法

方法成功条件失败条件
Promise.all()全部成功任何一个失败
Promise.race()最先完成的成功最先完成的失败
Promise.any()任何一个成功全部失败

常见写法

写法作用
new Promise(fn)创建 Promise
.then(fn)处理成功结果
.catch(fn)处理失败情况
.finally(fn)无论成败都执行
Promise.all([])等所有 Promise 完成
Promise.race([])取最快的 Promise
Promise.any([])取最先成功的 Promise

特点

  • Promise 有三种状态:pendingfulfilledrejected
  • 状态一旦改变就不会再变
  • .then() 可以链式调用,返回值传给下一个 .then()
  • .catch() 统一处理链式调用中的错误
  • .finally() 用于收尾操作
  • Promise.all() 适合需要同时执行多个请求的场景

注意事项

  • resolvereject 只能调用一次,多次调用会被忽略
  • .then() 中返回的值会自动包装成 Promise
  • .catch() 后面还可以接 .then(),用来处理错误后的逻辑
  • Promise.all() 中一个失败就全部失败,需要注意处理
  • 不要忘记写 .catch(),否则未处理的错误会被静默忽略

理解

可以把 Promise 理解为去餐厅点餐后拿到的取餐号。

点餐的时候(pending),菜还没做好,但已经拿到一个号了。

菜做好了(fulfilled),叫号去取,拿到食物(resolve 的值)。

如果这道菜卖完了(rejected),叫号告诉你没了(reject 的值)。

链式调用就像连续点了好几道菜:第一道上了才能点第二道,第二道上了才能点第三道。

Promise.all() 就像同时点了三道菜,等全部上齐了才能开吃。

Promise.race() 就像同时点了三道菜,哪个先上就先吃哪个。

引出

Promise 解决了回调地狱的问题,但链式调用写多了还是有点长。

JS async await 是更进一步的语法糖,可以让异步代码看起来和同步代码一样简洁。

在实际开发中,Promise 经常和 JS 网络请求 配合使用,fetch() 本身就返回一个 Promise。