Mithril Promise(executor)

Promise(executor)

描述

ES6 Promise 的 polyfill。

Promise 是一种使用异步计算的机制。

签名

promise = new Promise(executor)
参数 类型 是否必须 描述
executor (Function, Function) -> any 一个函数,决定了 promise 是完成还是拒绝
返回 Promise 返回 Promise

executor

executor(resolve, reject)
参数 类型 是否必须 描述
resolve any -> any 调用该函数来完成 promise
reject any -> any 调用该函数来拒绝 promise
返回 返回值会被忽略

静态成员

Promise.resolve

promise = Promise.resolve(value)
参数 类型 是否必须 描述
value any 要完成的值
返回 Promise 完成了 value 的 promise

Promise.reject

promise = Promise.reject(value)
参数 类型 是否必须 描述
value any 拒绝的值
返回 Promise 一个被拒绝的 promise,value 是被拒绝的原因

Promise.all

promise = Promise.all(promises)
参数 类型 是否必须 描述
promises Array 一个等待中的 promise 列表。如果其中一项不是 promise,就相当于在那一项上调用 Promise.resolve
返回 Promise 如果所有 promises 都已完成,则返回完成的 promise;只要其中有一个 promise 是拒绝的,就返回拒绝的 promise

Promise.race

promise = Promise.race(promises)
参数 类型 是否必须 描述
promises Array 一个等待中的 promise 列表。如果其中一项不是 promise,就相当于在那一项上调用 Promise.resolve
返回 Promise 只要其中一个 promise 被完成或拒绝,就会返回完成的 promise

实例成员

promise.then

nextPromise = promise.then(onFulfilled, onRejected)
参数 类型 是否必须 描述
onFulfilled any -> (any|Promise) 如果 promise 被完成,则会调用此函数。函数的第一个参数是 promise 完成的值。如果此函数返回的值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态。如果此函数抛出异常,nextPromise 会被拒绝。如果 onFulfillednull,则它会被忽略。
onRejected any -> (any|Promise) 如果 promise 被拒绝,则会调用此函数。函数的第一个参数是 promise 被拒绝的原因。如果函数的返回值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态,如果此函数抛出异常,nextPromise 会被拒绝。如果 onRejectednull,则它会被忽略。
返回 Promise 一个 promise,它的值取决于当前 promise 的状态

promise.catch

nextPromise = promise.catch(onRejected)
参数 类型 是否必须 描述
onRejected any -> (any|Promise) 如果 promise 被拒绝,则会调用此函数。此函数的第一个参数 promise 被拒绝的原因。如果此函数的返回值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态。如果此函数抛出异常,则 nextPromise 会被拒绝。如果 onRejectednull,则它会被忽略。
返回 Promise 一个 promise,它的值取决于当前 promise 的状态

工作原理

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 可以更好的重构代码,减少代码的嵌套。