Promise & Async/Await 使用指南「10 分钟精通 JS 异步利器」

🚀 个人简介:某大型测绘遥感企业资深Webgis开发工程师,软件设计师(中级)、优快云优质创作者
💟 作 者:柳晓黑胡椒❣️
📝 专 栏:js基础
🌈 若有帮助,还请关注点赞收藏,不行的话我再努努力💪💪💪

promise是什么

Promise 是 JavaScript 异步编程的一种解决方案,用于处理网络请求、定时器、文件读取等异步操作,解决传统回调函数的可读性和维护问题。

  • 语法层面
    📝 Promise 是一个对象,封装异步操作,并允许在未来获取操作结果。
    在 JS 中,异步操作不会阻塞主线程,Promise 提供统一的方式处理异步逻辑。
  • 功能层面
    🛠️ Promise 封装了异步操作,能提供成功结果(resolve)或失败原因(reject)。
    通过链式调用 then/catch,可以更清晰地处理异步结果。
  • 意义层面
    💡 Promise 就像一个“承诺”,承诺在未来某一时刻返回结果。
    使代码逻辑更清晰,避免立即处理异步结果带来的混乱。

promise的优点

🔥在 JavaScript 中,复杂业务逻辑容易产生嵌套回调。
过多嵌套会导致可读性差、维护困难,这就是所谓 回调地狱(callback hell)。
在这里插入图片描述

// 优化前
var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
};
  • 传统回调示例
// 优化前
var sayhello = function (name, callback) {
  setTimeout(function () {
    console.log(name);
    callback();
  }, 1000);
};
sayhello("first", function () {
  sayhello("second", function () {
    sayhello("third", function () {
      console.log("end");
    });
  });
});
  • 使用 Promise 后,可以通过 链式调用 改写:
    逻辑清晰,避免嵌套过深,代码更易维护。
sayhello("first")
  .then(function () {
    return sayhello("second"); //仍然返回一个 Promise 对象
  })
  .then(function () {
    return sayhello("third");
  })
  .then(function () {
    console.log("end");
  });
  • 可读性
    📚 Promise 对异步代码进行了规范化,相比回调函数,结构更直观、语义更清晰。
    每一步操作都明确表示 “成功执行” 或 “失败处理”,便于理解和调试。
  • 可靠性
    ✅ Promise 是原生支持的 API,浏览器间行为一致,保证了异步处理的可靠性。
    同时 Promise 自动捕获异常,防止错误被忽略。
  • 信任问题
    🔐 Promise 只能决议一次(resolve 或 reject),决议结果不可更改。
    绑定在 then/catch 中的回调也只会执行一次,避免重复触发。

promise的三种状态

  • pending:🟡初始状态,既不是成功,也不是失败。
  • fulfilled:🟢 操作成功,返回结果。
  • rejected:🔴 操作失败,返回原因。

🌀 说明:
pending 状态的 Promise 最终可能变为 fulfilled 并传递结果,或变为 rejected 并传递错误。
当状态变化时,绑定在 Promise 上的 then 方法会被触发。

  • 兼容性
    对低版本不支持的浏览器可以使用开源的 polyfill解决
  • 基础使用方式
    🛠️ Promise 的基本用法是 通过 new Promise 实例化,并结合 then/catch 处理成功或失败结果。

Promise接口

prmose.prototype.then

🔗 返回新的 Promise

  • 可以接收两个回调参数:成功回调失败回调
  • 回调函数返回的值会决定新 Promise 的状态
  • 如果回调中抛出错误,新的 Promise 会变为 rejected
  • thencatch 可以多次调用,实现 链式操作,方便处理连续异步任务

基础使用方式

var p1 = new Promise((resolve, reject) => {
  resolve("成功!");
  // or
  // reject(new Error("出错了!"));
});
p1.then(
  (value) => {
    console.log(value); // 成功!
  },
  (reason) => {
    console.error(reason); // 出错了!
  }
);

返回新的Promise

每次执行then之后都将返回新的Promise对象

回调没有定义,那么这个新的Promise状态将为原Promise的状态

var promise = new Promise(function(resolve, reject){
  reject('error')
})
var promise1 = promise
promise1.then(
  function(){
    console.log('success')
  },
  function(){
    console.log('error')
  }
);//输出error

回调中返回了新的状态,那么以新的状态为准

var promise = new Promise(function (resolve, reject) {
  reject("error");
});
var promise2 = promise.then(null, function () {
  return Promise.resolve("success");
});
promise2.then(
  function () {
    console.log("success");
  },
  function () {
    console.log("error");
  }
);

没有定义回调,那么以原来Promise状态为

var promise3 = Promise.reject("error");
promise3
  .then(() => {// 不会执行}, null)
  .then(
    () => {// 不会执行},
    (rs) => {
      console.log(rs); // error
    }
  );

回调中如果返回了Error,那么新的Promise状态为rejected

var promise = Promise.resolve("succes");
var promise4 = promise.then(function () {
  throw new Error("error");
});
promise4.then(null, function (reject) {
  console.log(reject);
});
//输出 Error: error

Promise可重复执行 then 或者 catch

var promise = Promise.resolve("succes");
promise
  .then(function () {
    console.log("one");
  })
  .then(function () {
    console.log("two");
  })
  .then(function () {
    console.log("three");
  }); // one two three

Promise.prototype.catch

🛑 添加 rejection 回调,返回新的 Promise

  • 捕获链式调用中抛出的异常
  • 保证程序在出现错误时不中断
    当这个回调函数被调用,新的promise将以它的返回值来 resolve,否则如果当前promise 进入 fulfilled 状态,则以当前promise的完成结果作为新promise的完成结果

使用链式语句的catch方法

var p1 = new Promise(function (resolve, reject) {
  resolve("succes");
});
p1.then(function (value) {
  console.log(value); //'success'
  return Promise.reject("oh,no!");
})
  .catch(function (e) {
    console.log(e); //'oh,no!'
  })
  .then(function () {
    console.log("success callback"); //'success callback'
  }, null);

捕获抛出的错误

// 拋出一个错误,大多数时候都将调用catch方法
var p1 = new Promise(function (resolve, reject) {
  throw "Un-oh!";
});
p1.catch(function (e) {
  console.log(e); //'Un-oh'
});
// 在异步函数中抛出的错误不会被catch捕获到
var p2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    throw "Error";
  }, 1000);
});
p2.catch(function (e) {
  console.log(e); //不会执行
});
// 在resolve()后面抛出的错误会被忽略
var p3 = new Promise(function (resolve, reject) {
  resolve();
  throw "Error";
});
p3.then(function (e) {
  console.log(e); //不执行
});

如果已解决错误

// 创建一个新的 Promise,且已解决
var p1 = Promise.resolve("success");
var p2 = p1.catch(function (res) {
  // 这个方法医院不会调用
  console.log(res);
});
p2.then(function (value) {
  console.log(value);//'succes'
}, null);

Promise.prototype.allSettled

📦返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果
注意:兼容性并不是很好,polyfill 也并未支持

var resolved = Promise.resolve(42);
var rejected = Promise.reject(-1);
var setTime = new Promise(function (resolve) {
  setTimeout(function () {
    resolve(111);
  }, 1000);
});
const pArr = Promise.allSettled([resolved, rejected]);
pArr.then(function (reason) {
  console.log(reason); // [{status: 'fulfilled', value: 42},{status: 'rejected', reason: -1}]
});

Promise.prototype.finally

🧹 无论 Promise 最终状态如何,都会执行回调
返回一个Promise。在promise结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式

var p1 = new Promise(function(resolve, reject){
  resolve('success')
})
var p2 = new Promise(function(resolve, reject){
  reject('error')
})
p1.finally(function(){
  console.log('one')
})
p2.finally(function(){
  console.log('two')
}) // one two

注意:由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况

Promise.all

等待多个 Promise 全部成功才触发成功
这个方法返回一个新的promise对象,该promise对象在参数对象中所有的promise对象都成功的时候才会触发成功,一旦有任何一个参数里面的promise对象失败则立即触发该promise对象的失败。
这个新的promise对象在触发成功状态以后,会把一个包含参数所有的 promise 返回值的数组作为成功回调的返回值,顺序跟参数的顺序保持一致。
如果这个新的 promise 对象触发了失败状态,它会把参数里第一个触发失败的 promise 对象的错误信息作为它的失败信息。
Promise.all 方法常被用于处理多个 promise 对象的状态合集

var p1 = Promise.resolve(3)
var p2 = 42
var p3 = new Promise(function(resolve,reject){
  setTimeout(resolve, 100,'foo')
})
Promise.all([p1,p2,p3]).then(function(reason){
  console.log(reason)
})// [3, 42,'foo']

Promise.reject

返回已拒绝的 Promise 对象
返回一个带有拒绝原因 reason 参数的 promise对象

Promise.reject('error').then(null,function(error){
  console.log(error) // 'error'
})
Promise.reject(new Error('fail')).then(null,function(error){
  console.log(error) // Error:fail
})

Promise.resolve

返回已解决的 Promise 对象
返回一个以给定值解析后的Promise对象。如果该值为 promise,返回这个 promise;
如果这个值是带有 "then"方法,返回的 promise 会采用它的最终状态;否则返回的 promise 将以此值完成

Promise.resolve('success').then(function(value){
  console.log(value)//'success'
},null)

resolve 另一个 promise

var original = promise.resolve(33)
var cast = Promise.resolve(original)
cast.then(function(value){
  console.log(value)
})
console.log(original === cast)//true 33

resolve 包含 then的对象参数

// resolve
var p1 = Promise.resolve({
  then:function(onFulfill, onReject){
    onFulfill('fulfilled!')
  }
})
p1.then(
  function(v){
    console.log(v)//'fulfilled!'
  },null
)
// reject
var p2 = Promise.resolve({
  then:function(onFulfill, onReject){
    onReject('rejected!')
  }
})
p2.then(null,function(e){
  console.log(e)//'rejected!'
})

Promise.race

🏁 多个 Promise 中只要有一个完成,就返回该 Promise 的结果
当参数里的任意一个 子promise 被成功或失败后,父promise 马上也会用 子promise 的成功返回值或失败详情作为参数调用 父promise 绑定的相应句柄,并返回该 promise对象

var  p1 = new Promise(function(resolve,reject){
  setTimeout(resolve,500,'one')
})
var p2 = new Promise(function(resolve,reject){
  setTimeout(resolve,100,'two')
})
Promise.race([p1,p2]).then(function(value){
  console.log(value)
}) // two

Promise事件

📢 使用Promise编写异步代码时,使用 reject 来处理错误。有时,开发者通常会忽略这一点,导致一些错误没有得到处理。例如

new Promise((resolve, reject) => {
  reject("error");
})
  .then(function () {})
  .then(function () {});

由于没有使用catch方法捕获错误,当前rejection 时,会派发此事件

  • unhandlerdrejection
    ⚡ 当 Promise 被拒绝,但没有提供 reject 函数来处理 rejection 时,会派发此事件
window.addEventListener("unhandledrejection", (e) => {
  console.log(e.reason); // 打印'error'
});
new Promise((resolve, reject) => {
  reject("error");
});
  • rejectionhandled
    ⚡ 当一个 Promise错误最初未被处理,但是稍后又得到了处理,则会派发此事件
window.addEventListener("unhandledrejection", (e) => { // 有promise reject未被处理
  console.log(e.reason); // 打印'error'
});
window.addEventListener("rejectionhandled", (e) => { // 有promise reject 已被被处理
  console.log(e.reason, "1111"); // 1秒后打印'error'
});
let foo = new Promise((resolve, reject) => {
  reject("error");
});
setTimeout(() => {
  foo.catch((e) => {});
}, 1000);

我们可以通过以上事件为 Promise 失败是提供补偿处理,也有利于调试 Promise 相关的问题。在每一个上下文中,该处理都是全局的。因此不管源码如何,所有的错误都会在同一个 handler 中被铺抓处理。

window.addEventListener(
  "unhandledrejection",
  (e) => {
    // 可以在这检查 e.promise 和 e.reason
    console.log(e.reason,e.promise)
    e.promise.catch((e)=>{})
    // 阻止默认处理(例如将错误输出到控制台)
    e.preventDefault();
  },
  false
);

高级用法

链式调用

🔗 连续执行两个或者多个异步操作是一个常见的需求 ,在上一个操作执行成功之后,开始下一个操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。

var promise = new Promise(function (resolve, reject) {
  resolve("promise");
});
promise
  .then(function (res) {
    console.log(res);
    return "success";
  })
  .then(function (res) {
    console.log(res);
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        reject("error");
      }, 3000);
    });
  })
  .then(null, function (reason) {
    console.log(reason);
    return "finish";
  })
  .then(function (res) {
    console.log(res);
  });
  // 输出 'promise'  'success'  'error' 'finish'

💡 注意:返回值默认为undefined,否则,callback 将无法获取上一个 Promise 的结果

Catch 的后续链式操作

🧩 有可能会在一个回调失败之后继续使用链式操作,即 使用一个catch,这对于链式操作中抛出一个失败之后,再次进行新的操作很有用。

new Promise((resolve, reject) => {
  console.log("初始化");
  resolve();
})
  .then(() => {
    throw new Error("有哪里不对了");
    console.log("执行[这个]");
  })
  .catch(() => {
    console.log("执行[那个]");
  })
  .then(() => {
    console.log("执行最后一个");
  }); //输出结果: 初始化 执行[那个] 执行最后一个

💡 注意:因为抛出了错误,'执行[这个]'没有被输出

执行顺序

⏱️ 为了避免意外,即使是一个已经变成 resolve 状态的 Promise,传递给 then() 的函数也总是会被异步调用

Promise.resolve().then(function () {
  console.log(2);
});
console.log(1); // 1  2

⚡ 传递到 then() 中的函数被置入了一个微任务队列,而不是立即执行,这意味着他是在 JavaScript 事件对了的所有运行时结束了,事件队列被清空之后,才开始执行

var loading = new Promise(function (resolve, reject) {
  setTimeout(resolve);
});
loading.then(function () {
  console.log(4);
});
Promise.resolve()
  .then(function () {
    console.log(2);
  })
  .then(function () {
    console.log(3);
  });
console.log(1);

嵌套

🌀 then 回调函数中如果返回了新的异步对象,那么后续链式调用的 then 都会等待新的异步对象完成才会继续向下执行

var p1 = new Promise(function (resolve, reject) {
  console.log("p1");
  resolve();
});
p1.then(function () {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      console.log("p2");
      resolve();
    }, 1000);
  });
})
  .then(function () {
    console.log("p3");
  })
  .then(function () {
    console.log("p4");
  })
  .then(function () {
    console.log("p5");
  }); // p1 p2 p3 p4 p5

⚡ 简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise,因为嵌套会导致可读性降低,代码也不容易排除,优化

Promise的缺点

  • ⚠️ 代码有风险造成未解决的promise
  • ❌ 无法取消Promise,一旦新建他就会立即执行,无法中途取消
  • ⚠️ 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
  • ⏳ 当处于 pending 状态时,无法得知目前进展到哪一个阶段 (刚刚开始还是即将完成)

async / await

✨ async / await 是 ES7 引入的异步代码规范。它提供了一种新的编写异步的方式,这种方式在语法层面提供了一种非常接近于同步代码的异步非阻塞代码风格,在此之前我们使用的多是异步回调,Promise 模式

使用方法

async关键字

🔹 async function 用来定义一个返回 AsyncFunction 对象的异步函数。异步函数是指通过事件循环异步执行的函数,它会通过一个隐式的 Promise 返回其结果

async function name([param, [param, [...param]]]) {
  statements;
}

name 函数每次 param 要传递给函数的参数 statements 函数体语法。 返回值 返回的 Promise对象会运行(resolve)异步函数的返回结果,或者运行拒绝(reject) ----- 如果异步函数抛出异常的话

await 关键字

⏳ await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用。
⏳ await 会暂停当前 async function 的执行,等待 Promise 处理完成。

  • 若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function
  • 若 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出。
  • 如果 await 操作符后的表达式的值不是一个 Promise,则返回该值本身。
[return_value] = await expression

表达式 一个Promise 对象或者任何要等待的值 返回值 返回 Promise对象的处理结果,如果等待的不是 Promise对象,则返回它本身

async function f1(){
  var x = await 10
  console.log(x) // 10
}
f1()

并联的await

⚡ async / await 语法的确好用简单,但在现实场景中也容易出现一些问题

async function retreProfile(email) {
  const user = await getUser(email);
  const roles = await getRoles(user);
  const level = await getLevel(user);
  return [user, roles, level];
}

上面代码实现了获取用户基本信息,然后通过基本信息获取用户角色、级别信息的功能,其中 getRoles 与 getLevel 两者之间并无依赖,是两个并联的异步操作,但代码中 getLevel 却需要等待 getRoles resolve之后才能执行。
并不是所有人都会犯这种错误,而是同步风格很容易诱惑我们忽略掉真正的异步调佣次序,而陷入过于简化的同步思维中。
💡 注意:async 只是形式上的同步,根本上还是异步的,请注意不要让使用者把事件浪费在无谓的等待上。

async function retriveProfile(email) {
  const user = await getUser(email);
  const p1 = getRoles(user);
  const p2 = getLevel(user);
  const [roles, levels] = await Promise.all([p1, p2]);
  return [user, roles, levels];
}

注意,代码中的getRolesgetLevel 函数都没有跟在 await关键字之后,而是把函数返回的 Promise 存放在变量 p1,p2 中,后续才对p1、p2 执行 await 声明,getRoles、getLevel 就能同事执行,不需要等待另一方的完成

错误处理

使用 try…catch

async function asyncCall() {
  try {
    // await asyncFunc();
    throw new Error("error");
  } catch (e) {
    console.log(e.message); //'error'
  }
}
asyncCall();

包装promise,使其返回统一的格式代码

// 包裝promise,使其返回统一的错误格式
// @param {Promise} promise
function to(promise) {
  // 第一个标识异常数据
  return promise.then((res) => [null, res]).catch((err) => [err]);
}
const [err, res] = await to(fetchUser(true));
if (err) {
  console.log("touser err", err);
}

继续使用catch

// 因为 async 返回的 promise对象,所以可以使用 catch
const user = await fetchUser(true).catch((err)=>{
  console.log(err)
})
  • 优点

  • 👍 async / await 从上到下,顺序执行,就像写同步代码一样。更符合人编写代码的习惯

  • 缺点

  • 编译后增加了代码的体积

  • 编译后的代码不容易理解,会给调试带来一直的困扰

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柳晓黑胡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值