目录

🛞 Promise 链式调用

🌰 例子 / Promise 链式调用:

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000)
}).then(result => result * 2)
  .then(result => result * 2)
	.then(result => console.log(result)) // 4
1
2
3
4
5

Promise 返回的结果通过 .then 处理程序链进行传递。只所以可以传递是因为每个对 .then 的调用都会返回一个新的 Promise 结果。

不会传递 Promise 的结果的情况(分开 .then ):

点击查看
let promise = new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});

promise.then(function(result) {
  alert(result); // 1
  return result * 2;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这里相当于是 平行的处理程序,彼此独立运行处理任务。这种属于极少情况。

# 在链式调用中返回 Promise

处理程序可以创建并返回一个 Promise,这样的情况下,其他的处理程序需要等待它的 状态确定(settled)获得其结果。

🌰 例子:

new Promise(function(resolve, reject) {
  setTimeout(() => resolve(1), 1000);
}).then(function(result) {
  
  return new Promise((resolve, reject) => { 
    setTimeout(() => resolve(result * 2), 1000);
  });
  
}).then(function(result) { 
  
  return new Promise((resolve, reject) => {
    setTimeout(() => resolve(result * 2), 1000);
  });
  
}).then(function(result) {
  cosole.log(result) // 4
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

这个 Promise 的链式调用中有三个 .then 。第一个 .then 返回一个新的 Promise 一秒之后会进行 resolve ,然后结果 (result) 回传递给第二个 .then 。第二个 .then 的动作与第一个 .then 相同。

返回 Promise 可以构建异步行为链。

🌰 例子 / 加载脚本 的例子:

loadScript("/article/promise-chaining/one.js")
  .then(function(script) {
    return loadScript("/article/promise-chaining/two.js");
  })
  .then(function(script) {
    return loadScript("/article/promise-chaining/three.js");
  })
  .then(function(script) {
    // 使用在脚本中声明的函数
    // 以证明脚本确实被加载完成了
    one();
    two();
    three();
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14

使用箭头函数重写:

loadScript("/article/promise-chaining/one.js")
  .then(script => loadScript("/article/promise-chaining/two.js"))
  .then(script => loadScript("/article/promise-chaining/three.js"))
  .then(script => {
    // 脚本加载完成,我们可以在这儿使用脚本中声明的函数
    one();
    two();
    three();
  });
1
2
3
4
5
6
7
8
9

提示

准确的说,处理程序 返回的并不是一个 Promise,而是一个 「thenable」 对象,一个具有方法 .then 的任意对象,它可以当做是一个 Promise 来对待。

可以自定义实现「Promise 兼容对象」:

点击查看

可以具有拓展的方法集,但也与原生的 Promise 兼容,因为它们实现了 .then 方法。

🌰 例子:

class Thenable {
  constructor(num) {
    this.num = num;
  }
  then(resolve, reject) {
    alert(resolve); // function() { native code }
    // 1 秒后使用 this.num*2 进行 resolve
    setTimeout(() => resolve(this.num * 2), 1000); // (**)
  }
}

new Promise(resolve => resolve(1))
  .then(result => {
    return new Thenable(result); // (*)
  })
  .then(alert); // 1000ms 后显示 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 链式调用实例: fetch

在前端中,通常使用 Promise 处理网络请求。

🌰 例子 / 向 user.json 发送请求,并从服务器加载该文本:

let promise = fetch(url)
1
fetch('/article/promise-chaining/user.json')
	.then((response) => response.text())
	.then((text) => console.log(text))
1
2
3

当全部文字内容从远程服务器下载完成后,它会返回一个 Promise,该 Promise 以刚刚下载完成的这个文本作为 result 进行 resolve

可以将 response 的远程内容解析为 JSON:

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => alert(user.name));
1
2
3

🌰 例子 / 上面的例子上,再向 GitHub 发送一个请求,加载用户个人资料并显示头像:

fetch('/article/promise-chaining/user.json')
  // 将其加载为 JSON
  .then(response => response.json())
  // 发送一个到 GitHub 的请求
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  // 将响应加载为 JSON
  .then(response => response.json())
  // 显示头像图片(githubUser.avatar_url)3 秒(也可以加上动画效果)
  .then(githubUser => {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => img.remove(), 3000); // (*)
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

为了使得链可拓展,需要返回一个在头像显示结束时进行 resolve 的 Promise:

fetch('/article/promise-chaining/user.json')
  .then(response => response.json())
  .then(user => fetch(`https://api.github.com/users/${user.name}`))
  .then(response => response.json())
  .then(githubUser => new Promise(function(resolve, reject) { // (*)
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser); // (**)
    }, 3000);
  }))
  // 3 秒后触发
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

所以,为了能让其中的请求能服用(可控制),每次的异步行为都应该返回一个 Promise,可以使得后续的行为成为可能。

最后,将代码拆分为可重用的函数:

function loadJson(url) {
  return fetch(url)
    .then(response => response.json());
}

function loadGithubUser(name) {
  return loadJson(`https://api.github.com/users/${name}`);
}

function showAvatar(githubUser) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = githubUser.avatar_url;
    img.className = "promise-avatar-example";
    document.body.append(img);

    setTimeout(() => {
      img.remove();
      resolve(githubUser);
    }, 3000);
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

使用例子:

loadJson('/article/promise-chaining/user.json')
  .then(user => loadGithubUser(user.name))
  .then(showAvatar)
  .then(githubUser => alert(`Finished showing ${githubUser.name}`));
1
2
3
4

# 总结

  • .then (或者 .catch / .finally 处理程序)会返回一个 Promise 的结果(value 或者 error),可以进一步传递。
  • 也可以返回一个 新的 Promise,要等待它的 状态确定,其结果才能进一步传递。

# 实例

# 区分不同的 Promise 调用

promise
  .then(f1)
  .catch(f2);
1
2
3

promise
  .then(f1, f2);
1
2
点击查看

这两种代码片段行为不相同。第一个中 f1 出现错误时,会被 catch(f2) 处理。但是在第二个中,不会被处理,因为 f1 下面没有处理来自 f1 的 Promise 结果(或者错误)的链。

📢 上次更新: 2022/09/02, 10:18:16