ES6 Promise 的 polyfill。
Promise 是一种使用异步计算的机制。
promise = new Promise(executor)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
executor |
(Function, Function) -> any |
是 | 一个函数,决定了 promise 是完成还是拒绝 |
返回 | Promise |
返回 Promise |
executor(resolve, reject)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
resolve |
any -> any |
否 | 调用该函数来完成 promise |
reject |
any -> any |
否 | 调用该函数来拒绝 promise |
返回 | 返回值会被忽略 |
promise = Promise.resolve(value)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
value |
any |
否 | 要完成的值 |
返回 | Promise |
完成了 value 的 promise |
promise = Promise.reject(value)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
value |
any |
否 | 拒绝的值 |
返回 | Promise |
一个被拒绝的 promise,value 是被拒绝的原因 |
promise = Promise.all(promises)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
promises |
Array |
是 | 一个等待中的 promise 列表。如果其中一项不是 promise,就相当于在那一项上调用 Promise.resolve |
返回 | Promise |
如果所有 promises 都已完成,则返回完成的 promise;只要其中有一个 promise 是拒绝的,就返回拒绝的 promise |
promise = Promise.race(promises)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
promises |
Array |
是 | 一个等待中的 promise 列表。如果其中一项不是 promise,就相当于在那一项上调用 Promise.resolve |
返回 | Promise |
只要其中一个 promise 被完成或拒绝,就会返回完成的 promise |
nextPromise = promise.then(onFulfilled, onRejected)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
onFulfilled |
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,它的值取决于当前 promise 的状态 |
nextPromise = promise.catch(onRejected)
参数 | 类型 | 是否必须 | 描述 |
---|---|---|---|
onRejected |
any -> (any|Promise) |
否 | 如果 promise 被拒绝,则会调用此函数。此函数的第一个参数 promise 被拒绝的原因。如果此函数的返回值不是 Promise,则返回值会作为完成 nextPromise 的值。如果返回值是 Promise,则 nextPromise 的值取决于 Promise 的内部状态。如果此函数抛出异常,则 nextPromise 会被拒绝。如果 onRejected 是 null ,则它会被忽略。 |
返回 |
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 可以链式调用。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()
。
在重构的版本中,没有必要对 getUserName
中 firstName
和 lastName
之间是否添加了空格这种功能进行测试。
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
中的请求失败,getProjectTitles
和 alert
也不会被调用,.catch
回调会记录错误。
如果 searchUsers
没有返回结果,错误处理程序也会捕获空引用异常,且 getFirstId
会尝试访问一个不存在的数组项的id
属性。
由于这些错误都是语义化的,容易保持每个函数足够小,且可测试,而不需要到处使用 try
/catch
。
有时,你已经有一个值,但希望把它包裹在 Promise 中。Promise.resolve
和 Promise.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)
某些情况下,你可能需要并行发送 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 可以更好的重构代码,减少代码的嵌套。