Skip to content

Promise 与案例 ​

本页对应 1-8Promise及案例,共 4 段视频,约 38 分钟。

学习目标 ​

  1. 能用自己的话说明 Promise 是浏览器内置对象,并描述其管理异步操作、获取成功或失败结果的作用。
  2. 能独立编写 Promise 三步使用代码:实例化并传入回调函数、在回调中执行异步操作并调用 resolve/reject、通过 then/catch 接收结果。
  3. 能准确说出 Promise 的 pending、fulfilled、rejected 三种状态及其对应触发条件,并能验证状态一旦改变后不可再次改变。
  4. 能使用 Promise 管理 XHR 请求省份列表,在异步成功后调用 resolve 传递数据,并通过 then 接收数据完成页面渲染。
  5. 能通过 XHR 的 status 属性判断请求是否成功,并在失败时使用 reject 传递 Error 对象,通过 catch 接收异常信息并渲染到页面。

知识点 ​

先明确:Promise 到底解决什么问题 ​

为什么需要:异步操作的结果不会立刻拿到,比如定时器、XHR 请求。过去我们容易把“发请求”和“拿到结果后怎么处理”写散,甚至层层嵌套。Promise 的价值是把异步操作“管理”起来,让成功走一条处理链,失败走另一条处理链,逻辑更清楚。

怎么想:笔记给的定义是:Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。换句话说:它是一个管理异步操作的对象,不仅能管理过程,还能把成功或失败的结果交给后续函数。还强调,学习 Promise 的另一个原因是理解 axios 内部运作机制——axios 的 .then() 拿成功结果、.catch() 拿失败结果,本质上就建立在 Promise 之上。

axios 内部基于 Promise 的机制示意

怎么做:先记住三个动作:实例化 Promise、执行异步操作并传递结果、用 then/catch 接收结果。

易错点:不要把 Promise 当成“立刻拿到结果”的工具;它管理的是异步结果,结果要通过 resolve/reject 传出去。

Promise 的基本语法与三步使用 ​

为什么需要:只背定义不会用,必须把“异步任务—结果传递—结果接收”串起来。

怎么想:笔记和 PPT 把步骤压成三步:1. 实例化 Promise 对象;2. 执行异步操作,并传递结果;3. 获取结果。成功调用 resolve,对应外部 .then();失败调用 reject,对应外部 .catch()。

Promise 管理异步任务的语法示意

怎么做:PPT 中的基础代码结构如下:

js
// 1. 实例化Promise对象
const p = new Promise((resolve,reject)=>{
  // 2. 执行异步操作,并传递结果
  // 成功resolve(成功结果) then()执行
  // 失败reject(失败结果) catch()执行
})
// 3. 接收结果
p.then(res=>{
  // 成功
}).catch(err=>{
  // 失败
})

实际演示中是先用定时器模拟异步:new Promise((resolve, reject) => { setTimeout(() => { resolve('成功了') }, 1000) }),然后写 p.then(res => { console.log(res) })。控制台一秒后打印“成功了”。失败测试则把 resolve 换成 reject('失败了'),外部改用 p.catch(err => { console.log(err) }),就能接收失败结果。

易错点:new Promise() 传入的回调函数会在实例化时立刻执行,所以异步代码要写在这个回调里面;resolve、reject 是函数,不是普通变量;成功和失败只走对应的一条链,不要在外层直接写结果。

顺带确认状态:pending、fulfilled、rejected ​

为什么需要:知道状态,才能理解为什么调用 resolve/reject 后 then/catch 会执行。

怎么想:调试时在控制台输入变量 p,展开后看 PromiseState。刚实例化时是 pending(待定);调用 resolve 后变成 fulfilled(已兑现);调用 reject 后变成 rejected(已拒绝)。状态改变后,才会触发对应的回调。

怎么做:写成功逻辑后,在控制台查看 p 的状态;写失败逻辑后再查看一次。实际还专门测试了“状态改变后能不能再变”:先 reject,再写 resolve,结果外部没有收到第二次成功结果,状态仍然是 rejected。

易错点:Promise 状态一旦从待定变成已兑现或已拒绝,就“敲定”了,不能再改。不要在一个 Promise 里既想成功又想失败地反复切换。

为什么它和 axios 长得像 ​

为什么需要:很多同学看到 .then()、.catch() 会觉得眼熟,因为 axios 就是这样用的。

怎么想:笔记说,Promise 的好处之一是“了解 axios 函数内部运作的机制”。需要明确:axios 是基于 Promise 和 XHR 封装而成的;then 接收成功结果,catch 接收失败结果。先学 Promise,再自己实现一个简易 hmAxios,就能更好理解 axios 原理。

怎么做:后续案例中,把定时器换成 XHR:在 Promise 回调里创建 XHR、open、监听 loadend,请求成功后用 resolve 传出数据;判断 xhr.status 在 200–299 之间算成功,否则用 reject(new Error(xhr.response)) 传出异常。外部仍然用 .then() 渲染数据,用 .catch() 渲染错误信息。PPT 中给出的调用形态是:

js
hmAxios({
  url: 'URL地址',
  // method:'get'
}).then(res => {
  // 成功
}).catch(err => {
  // 失败
})

function hmAxios(config) {
  // 略
}

易错点:真实请求里要判断响应状态码,不能只看“请求发出去了”;异常信息建议包装成 Error 对象再 reject,外部 catch 里通过 err.message 取信息。

小结:三问要能答上来 ​

  1. 什么是 Promise? 表示或管理一个异步操作最终状态和结果值的对象。
  2. 为什么学习 Promise? 成功和失败状态可以关联对应处理函数,也能帮助理解 axios 内部原理。
  3. 使用步骤? ① new Promise 执行异步任务;② 用 resolve 关联 then 的回调传递成功结果;③ 用 reject 关联 catch 的回调传递失败结果。

记住对应关系:resolve → then,reject → catch;状态一旦敲定不可逆。

为什么先认识 Promise 的状态 ​

上一节我们已经知道 Promise 的基本用法:实例化 Promise 对象,在回调函数里执行异步操作,成功调用 resolve,失败调用 reject,外部用 .then() 接收成功结果,用 .catch() 接收失败结果。这一节进一步说明:调用 resolve 或 reject 的本质,是改变 Promise 对象的状态;状态改变后,才会触发对应的处理函数。

为什么需要:如果不理解状态,就容易把 .then()、.catch() 当成“随便写的两个回调”,不知道它们为什么执行、什么时候执行。
怎么想:把 Promise 看成一个“管理异步操作的对象”,它内部有一个状态标识;异步操作完成后,通过 resolve / reject 把状态从“待定”改成“成功”或“失败”。
怎么做:先记住基本结构:

js
const p = new Promise((resolve, reject) => {
  // 执行异步操作,并传递结果
  // 成功:resolve(成功结果) -> then() 执行
  // 失败:reject(失败结果) -> catch() 执行
})

p.then(res => {
  // 接收成功结果
}).catch(err => {
  // 接收失败结果
})

易错点:resolve 对应 .then(),reject 对应 .catch();Promise 的回调函数在实例化时会立刻执行,但异步结果要等异步操作完成后再传递出去。

三种状态分别是什么 ​

每个 Promise 对象必定处于以下三种状态之一:

  1. 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  2. 已兑现(fulfilled):操作成功完成。
  3. 已拒绝(rejected):操作失败。

为什么需要:状态是 Promise 内部判断“该调用哪个处理函数”的依据。
怎么想:状态不是结果值本身。resolve('成功了') 中的 '成功了' 是结果值,状态变成 fulfilled 才是“成功”的标识。
怎么做:调试时先只实例化 Promise,在浏览器控制台输入变量 p 并展开,看到 PromiseState 为 pending;调用 resolve 后再查看,状态变为 fulfilled;调用 reject 后查看,状态变为 rejected。
易错点:fulfilled 不要拼错;pending 是初始状态,不是失败;不要试图手动修改状态字符串,状态应由 resolve / reject 改变。

状态改变后如何关联处理函数 ​

Promise 状态改变后触发对应处理函数

当状态从 pending 变为 fulfilled,会触发 .then() 里的回调;变为 rejected,会触发 .catch() 里的回调。

为什么需要:知道关联关系,才能把成功逻辑写在 .then(),失败逻辑写在 .catch()。
怎么想:resolve 是“成功”,所以对应 then;reject 是“失败”,所以对应 catch。
怎么做:用定时器模拟异步操作,一秒后调用 resolve:

js
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('成功了,很开心')
  }, 1000)
})

p.then(res => {
  console.log(res) // 成功了,很开心
})

失败时调用 reject:

js
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('嘤嘤嘤')
  }, 1000)
})

p.catch(err => {
  console.log(err) // 嘤嘤嘤
})

调试时,一秒后 .then() 或 .catch() 中的打印执行,说明状态已经改变并触发了对应回调。
易错点:成功结果不要写到 .catch() 里;.catch() 不是只捕获语法错误,它接收的是 reject 传出的失败结果。

状态一旦敲定就不能再改 ​

笔记强调:每个 Promise 对象一旦被兑现或拒绝,就是已敲定了,状态无法再被改变。

为什么需要:如果状态可以反复改变,同一个异步操作就可能既走成功又走失败,外部处理会混乱。
怎么想:Promise 表示“一个异步操作的最终完成或失败及其结果值”,既然叫“最终”,就只认第一次敲定。
怎么做:调试时先调用 reject,再调用 resolve:

js
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('失败了')
    resolve('我又成功了')
  }, 1000)
})

p.then(res => {
  console.log(res)
}).catch(err => {
  console.log(err) // 失败了
})

结果 .catch() 打印失败信息,后面的 resolve('我又成功了') 没有再传递出去;在控制台查看 p,状态仍然是 rejected。
易错点:不要在一个 Promise 里既 resolve 又 reject 还期待两次都生效;第一次敲定后,后续改变无效。

小结与自测 ​

  1. Promise 对象有哪 3 种状态?
答案
  • 待定 pending,已兑现 fulfilled,已拒绝 rejected
  1. Promise 状态有什么用?
答案
  • 状态改变后,用于关联对应的处理函数:fulfilled 触发 then,rejected 触发 catch

目标与整体思路 ​

这一节要把前面学过的 Promise 和 XHR 结合起来:用 Promise 管理 XHR 异步请求,获取省份列表并渲染到页面。为什么需要这样做?因为 XHR 本身通过事件回调拿结果,代码一多就容易分散;Promise 可以把“请求成功/失败”统一成 then/catch 两条路径,后续再封装接口也更自然。

怎么想?在这一节反复强调:Promise 里放什么异步操作都可以,之前放定时器,现在换成 XHR。XHR 请求成功后调用 resolve(数据),失败后调用 reject(错误信息),外部用 .then() 接收数据、用 .catch() 处理异常。

怎么做?先完成核心功能:创建 Promise → 在回调函数中执行 XHR → 成功后 resolve → .then() 中渲染。再加异常处理:用 xhr.status 判断 200~299,成功才解析数据,否则 reject(new Error(xhr.response))。

易错点:不要忘记 xhr.send();判断成功不能只写 status === 200;JSON.parse 要放在成功分支里,否则 404 时返回 HTML 文本会解析报错。

核心功能:请求省份列表并渲染 ​

为什么需要先做核心功能?因为只有先跑通“请求—传值—渲染”,才能知道 Promise 到底在管理哪一步。的设计是:XHR 代码写在 Promise 构造函数的回调里,Promise 实例化时这段逻辑立刻执行,请求也就发出去了。

怎么想?数据来自服务器,所以第一步在 Promise 回调中创建 XMLHttpRequest,调用 open 设置方法和地址,监听 loadend,再 send。拿到 xhr.response 后,为了外部使用方便,先在内部 JSON.parse 转成对象,再通过 resolve 传出去。

怎么做?核心代码结构如下:

js
const p = new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.open('get', '省份列表接口地址');

  xhr.addEventListener('loadend', () => {
    console.log(xhr.response);
    const data = JSON.parse(xhr.response);
    resolve(data);
  });

  xhr.send();
});

p.then(res => {
  document.querySelector('.box').innerHTML = res.list;
});

调试时先在 loadend 里打印响应,确认数据已经回来;再在 .then() 里打印 res,点开后发现真正要渲染的是 res.list,最后用 innerHTML 写进 .box。

易错点:open 只是配置请求,必须再调用 send;监听事件名是 loadend;如果外部还要做其他处理,内部先转好 JSON 对象会更方便。

调试异常:从 200 到 404 ​

为什么需要专门调试异常?因为真实请求不一定成功,必须知道失败时状态码和响应内容长什么样。先打印 xhr.status,正常请求得到 200;然后把 URL 后面加上 123 制造错误地址,再次请求得到 404。

怎么想?AJAX 判断响应成功不是看“有没有拿到文字”,而是看 HTTP 状态码。笔记小结也明确:响应状态码大于等于 200 且小于 300 才算成功。404 表示找不到资源,属于失败。

怎么做?在 loadend 中先判断:

js
if (xhr.status >= 200 && xhr.status < 300) {
  const data = JSON.parse(xhr.response);
  resolve(data);
} else {
  reject(new Error(xhr.response));
}

演示时发现:错误地址下 xhr.response 是一段 HTML 文本,里面有 404 not find 之类提示;如果仍然直接 JSON.parse,就会报错。所以必须把解析放到成功分支里。

易错点:不要写成只等于 200;不要先 JSON.parse 再判断状态码;失败时传出去的信息要包装成错误对象,方便 .catch() 统一处理。

异常处理:catch 中接收并渲染错误信息 ​

为什么要把异常传给 catch?因为 Promise 的成功走 .then(),失败走 .catch()。在 Promise 基础部分讲过:调用 resolve 对应 .then(),调用 reject 对应 .catch()。这里失败分支调用 reject(new Error(xhr.response)),外部 .catch() 就能拿到错误对象。

怎么想?最终页面要显示的是错误提示文本,而不是整个错误对象。用 console.dir(err) 查看对象结构,点开后找到 message 属性,再把 err.message 渲染到 .box。

怎么做?完整结构如下:

js
const p = new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.open('get', '省份列表接口地址');

  xhr.addEventListener('loadend', () => {
    if (xhr.status >= 200 && xhr.status < 300) {
      const data = JSON.parse(xhr.response);
      resolve(data);
    } else {
      reject(new Error(xhr.response));
    }
  });

  xhr.send();
});

p.then(res => {
  document.querySelector('.box').innerHTML = res.list;
}).catch(err => {
  console.dir(err);
  document.querySelector('.box').innerHTML = err.message;
});

易错点:catch 里不要直接拿 err 当字符串用,要取 err.message;渲染错误信息时页面盒子类名要和 HTML 对上;演示 404 后记得把 URL 改回正确地址。

小结 ​

这一节的关键不是背代码,而是理解 Promise 如何管理 XHR:异步请求写在 Promise 回调里,成功 resolve 数据,失败 reject 错误;外部用 .then() 渲染省份列表,用 .catch() 渲染异常提示。判断请求成功看状态码是否在 200~299 范围内。把这条链路跑通后,后面再封装类似 hmAxios 的函数就有了基础。

代码示例 ​

代码 1:Promise 基础用法(定时器模拟异步任务) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>认识 Promise</title>
</head>
<body>
  <script>
    // 1. 实例化 Promise 对象:传入的回调函数在实例化时「立刻执行」
    const p = new Promise((resolve, reject) => {
      // 2. 执行异步操作(这里用定时器模拟),并传递结果
      setTimeout(() => {
        const isOk = true
        if (isOk) {
          resolve('成功了,很开心')   // 成功 -> 状态变成 fulfilled
        } else {
          reject('嘤嘤嘤失败了')      // 失败 -> 状态变成 rejected
        }
      }, 1000)
    })

    // 3. 接收结果:resolve 对应 then,reject 对应 catch
    p.then(res => {
      console.log('then 收到成功结果:', res)
    }).catch(err => {
      console.log('catch 收到失败结果:', err)
    })

    // 观察状态变化:pending -> fulfilled / rejected(且不可再变)
    console.log('实例化后立刻打印 p:', p)        // 此时 promiseState 为 pending
    setTimeout(() => {
      console.log('1.5 秒后再打印 p:', p)         // 此时已是 fulfilled 或 rejected
    }, 1500)
  </script>
</body>
</html>

要点解释与易错点

  • new Promise((resolve, reject) => {...}) 里的回调函数是同步立刻执行的,异步代码(定时器、XHR)写在它内部。
  • resolve 传出的值只会进入 then 的参数;reject 传出的值只会进入 catch 的参数,两两对应,别记反。
  • 在控制台展开 Promise 对象可以看到 promiseState 属性:pending(待定)/ fulfilled(已兑现)/ rejected(已拒绝)。
  • 易错点:状态一旦从 pending 变成 fulfilled 或 rejected 就敲定了,之后再调 reject(或 resolve)不会生效,也不会触发对应回调。
  • 易错点:只写 then 不写 catch,失败时控制台会报未捕获的错误。

代码 2:Promise 管理 XHR —— 获取省份列表并渲染(核心功能) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Promise + XHR 获取省份列表</title>
</head>
<body>
  <button>获取省份列表</button>
  <div class="box"></div>

  <script>
    // 换成你自己的「获取省份列表」接口地址
    const URL = 'http://hmajax.itheima.net/api/province'

    document.querySelector('button').addEventListener('click', () => {
      // 1. 创建 Promise 对象,把 XHR 异步任务写在回调函数里
      const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', URL)

        // 监听 loadend 事件:响应回来后执行
        xhr.addEventListener('loadend', () => {
          // 2. 传递数据:把 JSON 字符串先转成 JS 对象,外面就不用再转了
          resolve(JSON.parse(xhr.response))
        })

        // 别忘了发送请求
        xhr.send()
      })

      // 3. 接收并使用数据
      p.then(res => {
        console.log(res)
        document.querySelector('.box').innerHTML = res.list
      })
    })
  </script>
</body>
</html>

要点解释与易错点

  • 要把某个异步任务交给 Promise 管理,就把它整段写进 new Promise 的回调函数里;这里替换了上一段的定时器,位置完全一样。
  • xhr.addEventListener('loadend', ...) 在响应回来后触发,xhr.response 是 JSON 字符串,先 JSON.parse 再 resolve 出去。
  • 易错点:忘记写 xhr.send(),请求根本发不出去,loadend 永远不会触发,then 也就永远不执行。
  • 易错点:open 的第一个参数是请求方法('GET'),第二个才是地址,顺序写反会直接报错。
  • res.list 是数组,直接赋给 innerHTML 会被转成字符串拼接展示,笔记中就是这么渲染的。

代码 3:Promise + XHR 异常处理(状态码判断 + catch 渲染错误信息) ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Promise + XHR 异常处理</title>
</head>
<body>
  <button>请求省份列表(异常演示)</button>
  <div class="box"></div>

  <script>
    // 故意写错地址,制造 404 异常;改回正确地址即可验证成功分支
    const URL = 'http://hmajax.itheima.net/api/province123'

    document.querySelector('button').addEventListener('click', () => {
      const p = new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()
        xhr.open('GET', URL)

        xhr.addEventListener('loadend', () => {
          // 2.1 判断请求是否成功:状态码 200 ~ 299 为成功
          if (xhr.status >= 200 && xhr.status < 300) {
            resolve(JSON.parse(xhr.response))
          } else {
            // 2.2 传递异常信息:包装成 Error 对象再 reject
            reject(new Error(xhr.response))
          }
        })

        xhr.send()
      })

      // 3. 接收并处理:成功走 then,失败走 catch
      p.then(res => {
        document.querySelector('.box').innerHTML = res.list
      }).catch(err => {
        console.dir(err)                       // 用 dir 才能展开看到 message
        document.querySelector('.box').innerHTML = err.message
      })
    })
  </script>
</body>
</html>

要点解释与易错点

  • 成功与否靠 xhr.status(响应状态码)判断:>= 200 && < 300 才算成功,404 属于「找不到」。
  • 失败分支必须调用 reject(...),catch 里的回调才会执行;不调 reject,catch 永远不会跑。
  • 把服务器返回的文本用 new Error(xhr.response) 包装后再传出,外部拿到的是标准错误对象,统一从 err.message 取信息。
  • 易错点:用 console.log(err) 打印错误对象看不到 message 属性内容,要换成 console.dir(err) 展开查看。
  • 易错点:异常时 xhr.response 是一段 HTML 文本,用 innerHTML 渲染才会显示成页面元素(笔记中演示的就是渲染出带 404 提示的标签)。
  • 易错点:错误分支里不要再执行 JSON.parse(xhr.response),格式不对会抛出解析异常。

常见错误 ​

1. then 里的回调永远不执行,页面也没动静

  • 现象:p.then(...) 写了,控制台一直没输出,Promise 对象展开后 state 仍是 pending。
  • 原因:异步分支里漏调了 resolve / reject;用 XHR 时则是 open 之后忘了 send(),loadend 根本不触发。
  • 修法:检查回调函数中每条执行路径是否都有 resolve 或 reject;XHR 写完 open 立刻补 send()。

2. 以为 resolve() 是 return,后面的代码照样执行

  • 现象:resolve('成功') 之后又跑了 console.log、又发了第二次请求。
  • 原因:resolve 只负责改状态 + 派发结果,不会中断函数执行。
  • 修法:成功分支写 return resolve(...),失败分支写 return reject(...),或直接用 if / else 把两条路隔开。

3. 只写 then 不写 catch,控制台报 Uncaught (in promise)

  • 现象:请求失败时页面无提示,只有一行红色报错,用户看到的是空白。
  • 原因:reject 抛出的结果没有对应的 catch 接收,被当成未处理的拒绝。
  • 修法:then 与 catch 成对书写;即便暂时不处理失败,也要写 catch 兜底。

4. 404 也进了 then,异常信息渲染不出来

  • 现象:把 URL 改错,then 依然执行,页面显示一堆解析失败的报错。
  • 原因:没判断 xhr.status,或写成了 status === 200,把 201、204 等也判成失败。
  • 修法:统一用区间判断 status >= 200 && status < 300,命中走 resolve,否则走 reject。

5. catch 里拿到 err,err.message 是 undefined

  • 现象:catch(err => box.innerHTML = err.message) 渲染出 undefined。
  • 原因:reject('服务器错误') 传的是字符串而不是错误对象,字符串没有 message 属性。
  • 修法:统一 reject(new Error(xhr.response));调试时用 console.dir(err) 展开对象确认属性名。

6. then 里 res.list 是 undefined

  • 现象:数据打印出来是一长串字符串,取 .list 拿不到数组。
  • 原因:resolve 出去的是 xhr.response 原始 JSON 文本,没解析就传给了外部。
  • 修法:在 Promise 内部 JSON.parse(xhr.response) 后再 resolve,让 then 只拿现成对象。

7. 状态已敲定后又调 reject,catch 不执行

  • 现象:代码里 resolve 和 reject 都调了,只有 then 生效,catch 静默。
  • 原因:Promise 状态一旦从 pending 变为 fulfilled 或 rejected 就锁定,后续调用被忽略。
  • 修法:保证成功/失败两条分支互斥(用 if / else),不要在同一路径上先后调用两者。

练习 ​

练习 1|把定时器装进 Promise 要求:用 new Promise 包装一个 2 秒定时器,成功时 resolve 一个对象 { code: 200, msg: '完成' };外部用 then 打印 msg。写完后把 resolve 改成 reject(new Error('超时')),补上 catch,让失败信息也能打印出来。

练习 2|改造回调式 XHR 给出这样一段代码:

js
function getProvinces() {
  const xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://hmajax.itheima.net/api/province')
  xhr.addEventListener('loadend', () => {
    console.log(xhr.response)
  })
  xhr.send()
}

要求:改造成 getProvinces() 返回 Promise 的版本,调用方写成 getProvinces().then(res => ...).catch(err => ...),并保证 then 里拿到的已经是 JS 对象,不是 JSON 字符串。

练习 3|找错并说明 下面代码有三处问题,请指出并给出修正后的完整写法:

js
const p = new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest()
  xhr.open('GET', 'http://hmajax.itheima.net/api/province')
  xhr.addEventListener('loadend', () => {
    resolve(xhr.response)
    if (xhr.status === 200) reject(new Error('失败'))
  })
})
p.then(res => console.log(res.list))

提示方向:请求是否发出、成功判断是否严谨、成功与失败是否互斥。

练习 4|异常信息上屏 要求:请求一个错误的地址(在原 URL 后随意加字符),在 catch 中把服务器返回的错误文本渲染到 .box 容器里,且页面上只出现提示文字本身,不出现 Error: 前缀、不出现 undefined。写完后说明你用 console.log 还是 console.dir 定位属性,为什么。

练习 5|状态推演 不看运行结果,先写出下面代码的控制台输出顺序,以及最后 p 的状态,再上机验证:

js
const p = new Promise((resolve, reject) => {
  console.log('A')
  setTimeout(() => {
    reject(new Error('x'))
    resolve('y')
  }, 0)
})
p.then(v => console.log('then', v))
p.catch(e => console.log('catch', e.message))
console.log('B')

原视频对照 ​

#视频时长
1认识-Promise08:16
2了解-Promise的状态09:53
3案例-使用Promise加XHR获取省份列表-核心功能09:12
4案例-使用Promise加XHR获取省份列表-异常处理10:44

小结 ​

  1. Promise 的真正价值是把"产出结果"和"消费结果"拆开:任务内部只负责产出,外部只负责处理,两边互不关心对方怎么写。
  2. resolve / reject 是"派发结果 + 改状态",不是 return,写在其后的同步代码仍会执行,靠 return 或 if / else 才能截断。
  3. 状态一旦敲定不可逆,所以成功与失败必须设计成互斥分支,任何"两个都调"的写法都是在埋雷。
  4. then 与 catch 要成对出现——不写 catch 不等于没有错误,只是错误变成了控制台里没人管的红色报错。
  5. 数据要在 resolve 之前处理干净(如 JSON.parse),then 里只做业务,这样后续换成 axios 时逻辑几乎不用改。
  6. XHR 的成功判断是 2xx 区间而不是等于 200;"Promise 永远 pending"最常见的原因不是 Promise 写错,而是 send() 忘了调。
  7. 错误一律用 Error 对象传递,保留 message,这是 catch 里能统一处理、统一展示的前提。
  8. Promise 本身并不会消灭回调,它只是把回调从"嵌套"改成"串联";真正让代码变平的,是后面基于它的链式调用与 async / await。