添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
高兴的花卷  ·  解决:The ...·  2 年前    · 
道上混的草稿纸  ·  Nginx - ...·  2 年前    · 
愤怒的苹果  ·  Cannot deserialize ...·  2 年前    · 
any -> (any|Promise) 如果 promise 被完成,则会调用此函数。函数的第一个参数是 promise 完成的值。如果此函数返回的值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态。如果此函数抛出异常, nextPromise 会被拒绝。如果 onFulfilled null ,则它会被忽略。 onRejected any -> (any|Promise) 如果 promise 被拒绝,则会调用此函数。函数的第一个参数是 promise 被拒绝的原因。如果函数的返回值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态,如果此函数抛出异常, nextPromise 会被拒绝。如果 onRejected null ,则它会被忽略。

Promise 是一个对象,表示将来可能会用到的值。

// 这个 promise 一秒钟后会被完成
var promise = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve("hello")
  }, 1000)
promise.then(function(value) {
  // 一秒钟后输出 "hello"
  console.log(value)
        

Promise 对于处理异步 API 很有用,例如 m.request

异步 API 通常需要花费很长时间来执行,因此使用 return 同步返回函数的值会太耗费时间。它们会在后台执行,在此期间 JavaScript 可以执行其他代码。当请求完成后,会用返回的结果来调用函数。

m.request 的执行需要时间,因为它会向远程服务器发送 HTTP 请求并等待响应,由于网络延迟,可能需要花费几百毫秒。

Promise 的链式调用

Promise 可以链式调用。then 回调函数的返回值可以作为下一个 then 回调的参数。这样可以把代码重构成较小的函数:

function getUsers() {return m.request("/api/v1/users")}
// 避免这种用法:因为很难对功能进行测试
getUsers().then(function(users) {
  var firstTen = users.slice(0, 9)
  var firstTenNames = firstTen.map(function(user) {return user.firstName + " " + user.lastName})
  alert(firstTenNames)
// 推荐这种用法:测试小函数会比较容易
function getFirstTen(items) {return items.slice(0, 9)}
function getUserName(user) {return user.firstName + " " + user.lastName}
function getUserNames(users) {return users.map(getUserName)}
getUsers()
  .then(getFirstTen)
  .then(getUserNames)
  .then(alert)

在上面经过重构的代码中,getUsers() 返回一个 Promise,我们链式调用了 3 个回调函数。当 getUsers() 完成后,getFirstTen 函数会被调用,且传入用户列表作为它的第一个参数,并返回前 10 个用户的列表。这 10 个用户的列表会作为 getUserNames 的第一个参数传入,并返回用户名列表。最后,弹出提示框显示用户名列表。

在上面的原始代码中,对功能进行测试会很困难,因为你必须发送一个 HTTP 请求才能运行代码,且函数的最后调用了 alert()

在重构的版本中,没有必要对 getUserNamefirstNamelastName 之间是否添加了空格这种功能进行测试。

Promise 合并

Promise 可以和其他 Promise 合并。这个功能使我们可以平铺嵌套的 Promise,使代码更易管理。

function searchUsers(q) {return m.request("/api/v1/users/search", {data: {q: q}})}
function getUserProjects(id) {return m.request("/api/v1/users/" + id + "/projects")}
// 避免这种用法:嵌套太深
searchUsers("John").then(function(users) {
  getUserProjects(users[0].id).then(function(projects) {
    var titles = projects.map(function(project) {return project.title})
    alert(titles)
// 建议这种用法:扁平的代码
function getFirstId(items) {return items[0].id}
function getProjectTitles(projects) {return projects.map(getProjectTitle)}
function getProjectTitle(project) {return project.title}
searchUsers("John")
  .then(getFirstId)
  .then(getUserProjects)
  .then(getProjectTitles)
  .then(alert)

在重构过的代码中,getFirstId 返回一个 id,并把该 id 作为第一个参数传入 getUserProjects,而 getUserProjects 又返回一个完成项目列表的 Promise。这个 Promise 是被合并的,所以 getProjectTitles 的第一个参数不是 Promise,而是项目列表。getProjectTitles 返回标题列表,这个列表最终会被显示在提示框中。

Promise 可以把错误传递到适当的错误处理函数。

searchUsers("John")
  .then(getFirstId)
  .then(getUserProjects)
  .then(getProjectTitles)
  .then(alert)
  .catch(function(e) {
    console.log(e)
        

这是之前的例子,并加上了错误处理。当网络断开时,searchUsers 函数会执行失败,导致产生错误。在这种情况下,不会触发 .then 回调,但 .catch 回调会把错误输出到控制台。

如果 getUserProjects 中的请求失败,getProjectTitlesalert 也不会被调用,.catch 回调会记录错误。

如果 searchUsers 没有返回结果,错误处理程序也会捕获空引用异常,且 getFirstId 会尝试访问一个不存在的数组项的id 属性。

由于这些错误都是语义化的,容易保持每个函数足够小,且可测试,而不需要到处使用 try/catch

有时,你已经有一个值,但希望把它包裹在 Promise 中。Promise.resolvePromise.reject 就是为了这个目的而存在的。

// 这个列表是从 localStorage 中读取的
var users = [{id: 1, firstName: "John", lastName: "Doe"}]
// `users` 存在与否取决于 localStorage 是否存在数据
var promise = users ? Promise.resolve(users) : getUsers()
promise
  .then(getFirstTen)
  .then(getUserNames)
  .then(alert)

多个 Promise

某些情况下,你可能需要并行发送 HTTP 请求,并在所有请求完成后执行代码。这可以通过 Promise.all 来实现:

Promise.all([
  searchUsers("John"),
  searchUsers("Mary"),
.then(function(data) {
  // data[0] 是名字是 John 的用户数组
  // data[1] 是名字是 Mary 的用户数组
  // 返回值等效于 [
  //   getUserNames(data[0]),
  //   getUserNames(data[1]),
  return data.map(getUserNames)
.then(alert)

上面的例子中,同时进行了两次用户搜索。一旦这两个搜索完成,我们会从两次搜索的结果中获取所有 userName,并显示在提示框中。

这个例子同时说明了小函数的另一个好处:我们可以重用上面创建的 getUserNames 函数。

为什么不使用回调

回调是处理异步计算的另一种机制。对于会执行多次的异步计算,使用回调会更合适(例如,onscroll 事件处理)。

但是,对于只会执行一次的异步计算,使用 Promise 可以更好的重构代码,减少代码的嵌套。