Skip to content

Promise

Promise,保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise 是一个对象,可以获取异步操作的消息。

  • 对象状态不受外界影响,只有异步操作可以决定当前状态
    • pending:进行中
    • fulfilled:已成功
    • rejected:已失败
  • 状态一旦改变,就不会再变
    • 两种状态变化:从pending变为fulfilled,从pending变为rejected
    • 实时获取:状态改变后,添加回调函数,会立即得到结果。而事件是错过了再去监听,无法得到结果

优点

  • 异步操作以同步操作流程表达,避免回调地狱
  • Promise 对象提供统一接口,控制异步操作更容易

缺点

  • 一旦执行,无法中途取消Promise
  • 不设回调,Promise内部错误,不会反应到外部
  • 处于pending状态,无法得知目前到哪一阶段(刚刚开始即将完成

基本用法

ES6 Promise对象是一个构造函数,用来生成Promise实例

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject(参数均为可选)。

  • resolve:在异步操作成功时调用,并将异步操作的结果作为参数传递出去。状态从 pending 到 fulfilled
  • reject在异步操作失败时调用,并将异步操作的错误作为参数传递出去。状态从 pending 到 rejected

Promise实例,用then方法两个参数分别指定resolved状态和rejected状态的回调函数

js
promise.then(
  function (value) {
    // success
  },
  function (error) {
    // failure
  }
)

Promise 新建后立即执行

js
let promise = new Promise(function (resolve, reject) {
  console.log('Promise')
  resolve()
})

promise.then(function () {
  console.log('resolved.')
})

console.log('Hi!')

// Promise
// Hi!
// resolved

代码执行:p2 返回 p1 ,p2 返回一个 Promise,导致 p2 状态无效,依赖于 p1 状态。

1 秒后返回 p1,3 秒后,p1 变为 rejected,出发 catch

js
const p1 = new Promise(function (resolve, reject) {
  setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
  setTimeout(() => resolve(p1), 1000)
})

p2.then((result) => console.log(result)).catch((error) => console.log(error))

状态传递:p1状态会传递给p2

  • 如果p1pendingp2回调等待p1状态改变
  • 如果p1状态时resolvedrejectedp2回调立刻执行

resolvereject不会终结 promise 参数执行

js
new Promise((resolve, reject) => {
  resolve(1)
  console.log(2)
}).then((r) => {
  console.log(r)
})
// 2
// 1

执行时机:resolvedPromise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务

Promise 原型方法

Promise.prototype.then

Promise.prototype.then():定义在对象原型上,为Promise实例添加状态改变时回调函数

返回Promise实例,可以链式运算

js
getJSON('/posts.json')
  .then(function (json) {
    return json.post
  })
  .then(function (post) {
    // ...
  })

链式调用时,等待前一个 Promise 状态变化

Promise.prototype.catch

Promise.prototype.catch():指定发生错误时的回调函数。是.then(null, rejection).then(undefined, rejection)的别名。

js
.then(null, rejection)
.then(undefined, rejection)

// catch是上述两种写法的别名
Promise.prototype.catch()

注意事项

  1. 不要在then()中定义Reject状态回调函数,应使用catch(),因为更接近同步(try catch)写法
  2. promise抛出一个错误,就被catch()方法指定的回调函数捕获。以下三种写法等效
js
// 写法一
const promise = new Promise(function (resolve, reject) {
  throw new Error('test')
})
// 写法二
const promise = new Promise(function (resolve, reject) {
  try {
    throw new Error('test')
  } catch (e) {
    reject(e)
  }
})
// 写法三
const promise = new Promise(function (resolve, reject) {
  reject(new Error('test'))
})

// 捕获错误
promise.catch(function (error) {
  console.log(error)
})
  1. 异步操作抛出错误,状态就会变为rejected,就会调用catch()处理这个错误

  2. then()回调函数,如果运行中抛出错误,也被catch()方法捕获

  3. 状态改变后,抛出错误无效

js
const promise = new Promise(function (resolve, reject) {
  resolve('ok')
  throw new Error('test')
})
promise
  .then(function (value) {
    console.log(value)
  })
  .catch(function (error) {
    console.log(error)
  })
// ok
  1. Promise 对象的错误会一直向后传递,直到被捕获为止(错误总是会被下一个catch语句捕获)
js
getJSON('/post/1.json')
  .then(function (post) {
    return getJSON(post.commentURL)
  })
  .then(function (comments) {
    // some code
  })
  .catch(function (error) {
    // 处理前面三个Promise产生的错误
  })

与 try/catch 代码块区别

如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码。不会中断外层代码,“Promise 会吃掉错误”

js
const someAsyncThing = function () {
  return new Promise(function (resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2)
  })
}

someAsyncThing().then(function () {
  console.log('everything is great')
})

setTimeout(() => {
  console.log(123)
}, 2000)
// Uncaught (in promise) ReferenceError: x is not defined
// 123

catch 抛出错误,需要之后的 catch 捕获

js
someAsyncThing()
  .then(function () {
    return someOtherAsyncThing()
  })
  .catch(function (error) {
    console.log('oh no', error)
    // 下面一行会报错,因为y没有声明
    y + 2
  })
  .catch(function (error) {
    console.log('carry on', error)
  })
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]

Promise.prototype.finally

ES2018 - Promise.prototype.finally():不管 Promise 对象最后状态如何,都会执行的操作。

  • 回调函数不接受参数,意味无法知道状态为 fulfilled 还是 rejected
  • finally本质上是then方法的特例
js
promise.finally(() => {
  // 语句
})

// 等同于
promise.then(
  (result) => {
    // 语句
    return result
  },
  (error) => {
    // 语句
    throw error
  }
)

polyfill

js
Promise.prototype.finally = function (callback) {
  let P = this.constructor
  return this.then(
    (value) => P.resolve(callback()).then(() => value),
    (reason) =>
      P.resolve(callback()).then(() => {
        throw reason
      })
  )
}

finally方法:总是会返回原来的值

js
// resolve 的值是 undefined
Promise.resolve(2).then(
  () => {},
  () => {}
)

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(
  () => {},
  () => {}
)

// reject 的值是 3
Promise.reject(3).finally(() => {})

Promise 方法

Promise.all

Promise.all():将多个 Promise 实例,包装成一个新的 Promise 实例。

js
const p = Promise.all([p1, p2, p3])
  • 参数可以不是数组,但必须具有 Iterator 接口
  • 参数都不是 Promise,会调用 Promise.resolve
  • 返回的每个成员都是 Promise 实例

只有p1p2p3状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3返回值组成一个数组,传递给p回调函数

只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

定义了自己的 catch,不会触发 Promise.all 方法 catch

js
const p1 = new Promise((resolve, reject) => {
  resolve('hello')
})
  .then((result) => result)
  .catch((e) => e)

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了')
})
  .then((result) => result)
  .catch((e) => e)

Promise.all([p1, p2])
  .then((result) => console.log(result)) // ["hello", Error: 报错了]
  .catch((e) => console.log(e))

如果p2没有自己的catch方法,就会调用Promise.all()catch方法

js
const p1 = new Promise((resolve, reject) => {
  resolve('hello')
}).then((result) => result)

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了')
}).then((result) => result)

Promise.all([p1, p2])
  .then((result) => console.log(result))
  .catch((e) => console.log(e)) // Error: 报错了

Promise.race

Promise.race():多个 Promise 实例,包装成一个新的 Promise 实例

js
const p = Promise.race([p1, p2, p3])
  • 参数都不是 Promise,会调用 Promise.resolve

只要p1p2p3之中有一个实例率先改变状态p的状态就跟着改变。率先改变的 Promise 实例的返回值,就传递给p的回调函数

应用:异步请求超时处理

js
const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
])

p.then(console.log).catch(console.error)

Promise.allSettled

ES2020 - Promise.allSettled():接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。等到所有参数实例都返回结果,包含 fulfilled 和 rejected 两种情况。

返回新 Promise 实例状态总是 fulfilled,不会为 rejected。状态为 fulfilled 时,会向回调函数中传入参数数组。

js
const resolved = Promise.resolve(42)
const rejected = Promise.reject(-1)

const allSettledPromise = Promise.allSettled([resolved, rejected])

allSettledPromise.then(function (results) {
  console.log(results)
})
// [
//    { status: 'fulfilled', value: 42 },
//    { status: 'rejected', reason: -1 }
// ]

返回数组里面 有 rejected 和 fulfilled 状态,但是 Promise 实例状态为 fulfilled

参数数组:每个对象都有status属性,成功为 value,失败为 reason

js
// 异步操作成功时
{status: 'fulfilled', value: value}

// 异步操作失败时
{status: 'rejected', reason: reason}

可以通过 status 过滤出 所有的 rejected 和 fulfilled 请求

与 Promise.all 区别

Promise.all()无法确定所有请求都结束。Promise.allSettled(),可以保证所有请求结束,不管结果。

js
const urls = [
  /* ... */
]
const requests = urls.map((x) => fetch(x))

try {
  await Promise.all(requests)
  console.log('所有请求都成功。')
} catch {
  console.log('至少一个请求失败,其他请求可能还没结束。')
}

Promise.any

ES2021 - Promise.any():接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。

  • 有一个变成fulfilled状态,包装实例就会变成fulfilled状态

  • 所有参数实例都变成rejected状态,包装实例就会变成rejected状态

与 Promise.race 区别

Promise.any()Promise.race()相似,但Promise.any()不会因为某个 Promise 变成rejected状态而结束

Promise.any()抛出的错误是一个 AggregateError 实例,相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误

js
var resolved = Promise.resolve(42)
var rejected = Promise.reject(-1)
var alsoRejected = Promise.reject(Infinity)

Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  console.log(result) // 42
})

Promise.any([rejected, alsoRejected]).catch(function (results) {
  console.log(results) // [-1, Infinity]
})

Promise.resolve

Promise.resolve():将现有对象转为 Promise 对象

js
Promise.resolve('foo')
// 等价于
new Promise((resolve) => resolve('foo'))

参数的四种情况

  1. Promise 实例:将不做任何修改、原封不动地返回这个实例

  2. thenable对象:将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法

js
let thenable = {
  then: function (resolve, reject) {
    resolve(42)
  }
}

let p1 = Promise.resolve(thenable)
p1.then(function (value) {
  console.log(value) // 42
})

thenable对象:具有then方法的对象

  1. 参数不是具有then()方法的对象,或根本就不是对象:返回一个新的 Promise 对象,状态为resolved
js
const p = Promise.resolve('Hello')

p.then(function (s) {
  console.log(s)
})
// Hello
  1. 不带有任何参数:直接返回一个resolved状态的 Promise 对象
js
setTimeout(function () {
  console.log('three')
}, 0)

Promise.resolve().then(function () {
  console.log('two')
})

console.log('one')

// one
// two
// three

setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行

Promise.reject

Promise.reject(reason):返回一个新的 Promise 实例,该实例的状态为rejected

js
const p = Promise.reject('出错了')
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
  console.log(s)
})
// 出错了

Promise.reject()方法的参数是一个字符串,后面catch()方法的参数e就是这个字符串

Promise.try

场景:不知道或者不想区分函数f是同步函数还是异步操作,但想用 Promise 来处理。这样不管f是否包含异步操作,都可以thencatch方法处理f抛出的错误。一般采用以下写法

js
Promise.resolve().then(f)

问题:f是同步函数,但通过Promise后,变成异步操作,在本轮事件循环末尾执行。

js
const f = () => console.log('now')
Promise.resolve().then(f)
console.log('next')
// next
// now

预期:同步代码同步执行,异步代码异步执行,具有统一API

解决:

  1. async 函数写法
js
const f = () => console.log('now')

(async () => f())()
.then(...)
.catch(...)

console.log('next');
// now
// next

async吃掉 抛出错误,使用promise.catch方法捕获

  1. new Promise()
js
const f = () => console.log('now')
;(() => new Promise((resolve) => resolve(f())))()
console.log('next')
// now
// next
  1. Promise.try()

Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。

js
const f = () => console.log('now')
Promise.try(f)
console.log('next')
// now
// next

应用

抛出异步错误,通过 catch 捕获

js
database.users.get({id: userId})
.then(...)
.catch(...)

抛出同步错误,需要用 try...catch 捕获

js
try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}
js
try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}

Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块

ployfill

js
export function promiseTry(func) {
  return new Promise(function (resolve, reject) {
    resolve(func())
  })
}

应用

加载图片

js
function loadImageAsync(url) {
  return new Promise(function (resolve, reject) {
    const image = new Image()

    image.onload = function () {
      resolve(image)
    }

    image.onerror = function () {
      reject(new Error('Could not load image at ' + url))
    }

    image.src = url
  })
}

封装 AJAX

js
const getJSON = function (url) {
  const promise = new Promise(function (resolve, reject) {
    const handler = function () {
      if (this.readyState !== 4) {
        return
      }
      if (this.status === 200) {
        resolve(this.response)
      } else {
        reject(new Error(this.statusText))
      }
    }
    const client = new XMLHttpRequest()
    client.open('GET', url)
    client.onreadystatechange = handler
    client.responseType = 'json'
    client.setRequestHeader('Accept', 'application/json')
    client.send()
  })

  return promise
}

getJSON('/posts.json').then(
  function (json) {
    console.log('Contents: ' + json)
  },
  function (error) {
    console.error('出错了', error)
  }
)

Generator 函数与 Promise 的结合

Generator 函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。

js
function getFoo() {
  return new Promise(function (resolve, reject) {
    resolve('foo')
  })
}

const g = function* () {
  try {
    const foo = yield getFoo()
    console.log(foo)
  } catch (e) {
    console.log(e)
  }
}

function run(generator) {
  const it = generator()

  function go(result) {
    if (result.done) return result.value
    return result.value.then(
      function (value) {
        return go(it.next(value))
      },
      function (error) {
        return go(it.throw(error))
      }
    )
  }

  go(it.next())
}

run(g)

相关链接

[-] Promise