进阶篇:Promise 对象

Promise 把「尚未完成的异步结果」做成一个对象:调用方先拿到这个对象,再在成功或失败时接上处理函数。它让异步流程可以像同步那样传递返回值,而不必把回调层层嵌进去。

什么是 Promise?

Promise 是表示异步操作最终完成(成功或失败)及其结果值的对象。构造函数 new Promise(executor) 会立刻同步执行 executor;异步方法通常返回一个 Promise,以便在未来把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending):初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled):意味着操作成功完成。
  • 已拒绝(rejected):意味着操作失败。

备注: 如果一个 promise 已经被兑现(fulfilled)或被拒绝(rejected),那么我们也可以说它处于已敲定(settled)状态。您还会听到一个经常跟 promise 一起使用的术语:已决议(resolved),它表示 promise 已经处于已敲定状态,或者为了匹配另一个 promise 的状态被"锁定"了。

Promise 对象的限制:

  • 一旦 executor 开始执行就不能「取消这个 Promise」。要取消的是底层任务,现行做法是配合 AbortController(如 fetchsignal)。Promise 状态一旦敲定就不能改回去。
  • 处于 pending 时,无法得知目前进展到哪一阶段(刚刚开始还是即将完成)。
  • 若没有 catch(也没有 unhandledrejection 监听),内部抛出的错误不会变成外层同步异常,表现为未处理的拒绝。

基础用法

const promise = new Promise((resolve, reject) => {
  // ... some code
  if (/* 异步操作成功 */){
    resolve(value)
  } else {
    reject(error)
  }
})

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolverejectresolve 将状态从 pending 变为 fulfilled(操作成功时调用);reject 将状态从 pending 变为 rejected(操作失败时调用)。

promise.then(value => {
    // success
}, err => {
    // failure
});

注意: Promise 实例生成后会立即执行

Promise 的一些特性:

  • Promise 是微任务: then / catch / finally 的回调作为微任务,在当前宏任务结束后、下一宏任务开始前执行。setTimeout(fn, 0)宏任务,要等当前宏任务以及已排队的微任务都跑完才会进下一轮。即使已经超过指定延迟,也仍可能要等当前队列处理完毕。

  • Promise 链式调用: then()、catch()、finally() 方法将进一步的操作与一个变为已敲定状态的 promise 关联起来。这些方法还会返回一个新生成的 promise 对象,这个对象可以被非强制性的用来做链式调用。

常见使用场景

// 图片加载
function loadImage(url) {
    return new Promise((resolve, reject) => {
        const image = new Image()
        image.onload = function () {
            resolve(image)
        }
        image.onerror = function () {
            reject(new Error(`加载出错: ${url}`))
        }
        image.src = url
    })
}

loadImage('./assets/img/pins_3338674420.jpg').then(
    res => console.log('加载成功!'),
    err => console.error(err)
)
// Ajax 请求(现行也可用 fetch,此处保留 XMLHttpRequest 示例)
function getText(url) {
    return new Promise((resolve, reject) => {
        const http = new XMLHttpRequest()
        http.open("GET", url)
        http.onreadystatechange = function () {
            if (this.readyState !== 4) {
                return
            }
            if (this.status === 200) {
                return resolve(this.response)
            } else {
                return reject(new Error(this.statusText))
            }
        }
        http.send()
    })
}

getText("./assets/other/%E7%BE%8E%E4%BA%BA%E8%B0%B7%20-%20%E9%98%BF%E5%85%B0.vtt").then(
    json => console.log('Contents: ' + json),
    err => console.error(err)
)

静态方法

const p1 = Promise.resolve('p1: Success!')
const p2 = Promise.reject('p2: Error!')
const p3 = Promise.resolve('p3: Success!')

all()

all() 接收一个可迭代对象(元素不必已是 Promise,会被 Promise.resolve 包装)。全部兑现时,返回的 Promise 兑现为一个结果数组(顺序与传入顺序一致);其中任一拒绝时,立即以该原因拒绝,不等待其余项。

  • 完成(Fulfillment): 如果传入 iterable 为空,或者所有传入的 promise 都变为完成状态,或者传入的可迭代对象内没有 promise,则返回一个已完成 resolved 状态的 promise。在任何情况下,该返回的 promise 的完成状态的结果都是一个数组,它包含所有的传入迭代参数对象的值(也包括非 promise 值)。

  • 失败/拒绝(Rejection): 如果传入的 promise 中有一个失败(rejected),则将失败 promise 的结果传递给失败状态的回调函数,而不管其它 promise 是否完成。

Promise.all([p1, p3]).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})
//  ['p1: Success!', 'p3: Success!']
Promise.all([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})
// p2: Error!

allSettled()

allSettled() 方法接收一个 Promise 实例的可迭代对象,在所有给定的 promise 都已经 fulfilled 或 rejected(即,无论是成功的达成或被拒绝)后,返回一个新的 Promise 实例。这个新的 Promise 实例的参数是一个对象数组,每个对象表示对应的 Promise 实例的结果。

每个结果对象,都有一个 status 字符串,如果它的值为 fulfilled,则结果对象上存在一个 value;如果值为 rejected,则存在一个 reason 。

Promise.allSettled([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})
// (3) [{…}, {…}, {…}]
// 0: {status: 'fulfilled', value: 'p1: Success!'}
// 1: {status: 'rejected', reason: 'p2: Error!'}
// 2: {status: 'fulfilled', value: 'p3: Success!'}
// length: 3
// [[Prototype]]: Array(0)

备注: allSettled() 方法不关心异步操作的结果,只关心这些操作有没有结束。

any()

any() 方法接收一个 Promise 实例的可迭代对象,只要其中的一个 Promise 实例成功,返回一个新的成功 Promise 实例,参数是成功 Promise 实例的结果。

如果可迭代对象中没有一个 Promise 实例成功,则返回一个失败的 Promise 实例,参数是 AggregateError 类型(Error 的一个子类,用于把单一的错误集合在一起)的实例。

Promise.any([p2, p2, p2]).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})
// AggregateError: All promises were rejected

本质上,这个方法和 all() 方法是相反的。

注意: any() 已是标准方法(ES2021),现行浏览器均支持。

race()

race() 接收一个可迭代对象,只要其中一项敲定(兑现或拒绝),返回的 Promise 就以同样方式敲定。结果可能是成功,也可能是失败,取决于最先敲定的那一项。

Promise.race([p1, p2, p3]).then(res => {
    console.log(res)
}).catch(err => {
    console.error(err)
})
// p1: Success!

如果传的迭代是空的,则返回的 Promise 实例将永远等待。

resolve()

resolve() 方法返回一个以给定值解析后的 Promise 对象。

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

该方法的参数分成四种情况:

  • 参数是一个 Promise 实例:不做任何修改、原封不动地返回这个实例。

  • 参数是一个 thenable 对象:将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

    // thenable对象指的是具有then方法的对象
    const thenable = {
      then: function(resolve, reject) {
        resolve(42);
      }
    };
    
    const p1 = Promise.resolve(thenable);
    p1.then(function(value) {
      console.log(value); // 42
    });
    
  • 参数不是具有 then 方法的对象,或根本就不是对象:如果参数是一个原始值,或者是一个不具有 then 方法的对象,则返回一个新的 Promise 对象,状态为 resolved。resolve() 方法的参数,会同时传给回调函数。

  • 不带有任何参数:直接返回一个 resolved 状态的 Promise 实例。

reject()

reject() 方法也会返回一个新的 Promise 实例,该实例的状态为 rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

注意: 该方法的参数,会原封不动地作为拒绝的理由传递给后续的回调函数。这一点与 resolve() 方法不同。

try()

Promise.try() 用来统一处理同步函数和异步操作:同步抛错会变成拒绝,返回值会被 Promise.resolve 包装。现行规范已纳入该方法,较新浏览器已支持;旧环境仍可用下面的垫片写法。最初见提案

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

上面的写法有一个缺点:如果 f 是同步函数,它会在本轮微任务里执行,并不会像直接调用那样立即执行。

那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?

回答是可以的。

还有以下两种写法:

const f = () => console.log('now')
(async () => f())()
console.log('next')
// now
// next
const f = () => console.log('now');
(
    () => new Promise(resolve => resolve(f()))
    // 或者
    // () => Promise.resolve(f())
)()
console.log('next')
// now
// next

try() 方法替代上面的写法:

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

实例方法

then()

then() 方法返回一个 Promise,作用是为 Promise 实例添加状态改变时的回调函数。

then() 方法有两个参数(都是可选的),是给定的 Promise 的成功和失败情况的回调函数。

const genPromise = (value) => new Promise((resolve, reject) => {
    if (value === 1) {
        return resolve(value)
    }
    return reject('Error')
})
genPromise(1).then(res => console.log('Success: ', res), err => console.error(err))
genPromise(1).then(res => console.log('Success: ', res), null)
genPromise().then(null, err => console.error(err))
  • then() 方法的第一个参数是 resolved 状态的回调函数,第二个参数是 rejected 状态的回调函数。这两个回调函数都是可以传参数的。

  • resolve 函数的参数除了正常的值以外,还可以是另一个 Promise 实例。如果 resolve 方法返回的是一个Promise 对象(即有异步操作),那么,后一个 then 方法中的第一个回调函数,就会等待该 Promise 对象的状态发生变化,才会被调用。

  • reject 函数的参数通常是 Error 对象的实例,表示抛出的错误;

  • then() 方法中,resolve 和 reject 回调函数后面还可以执行其它语句,但是,一般不建议这么做。

  • then() 方法返回的是一个新的 Promise 实例(不是原来的 Promise 实例)。

  • then() 方法中可以用 return 来返回参数,在后一个 then 的回调函数中接收。

    Promise.resolve().then(res => {
        return 'aa'
    }).then(res => {
        console.log(res) //aa
    }, err => {
        console.error(err)
    })
    

catch()

catch() 方法返回一个Promise 实例,并且处理拒绝的情况。它的行为与调用 .then(undefined, onRejected) 相同。

catch() 方法是 .then(null, rejection).then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。事实上,catch() 方法内部就是调用 .then(undefined, onRejected) 实现的。

loadImage('./img/pins_3338674420.jpg').then(res => {
    console.log('加载成功!')
}).catch(err => {
    console.error(err)
})

上面代码中,loadImage() 方法返回一个 Promise 对象,如果异步操作成功,则调用 then() 指定的回调;如果失败,则会调用 catch() 处理这个错误。另外,then() 指定的回调如果运行中抛出错误,也会被 catch() 捕获。

catch() 的一些注意事项:

  • Promise 状态已经变成 resolved,再抛出错误是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

    const promise = new Promise((resolve, reject) => {
        resolve('ok')
        throw new Error('test') // 无法被 catch捕获
    });
    promise.then(function(value) {
        console.log(value) // ok
    }).catch(error => {
        console.log(error)
    });
    
  • Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch() 方法捕获。

  • 没有使用 catch() 方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码,即不会有任何反应。

  • 一般来说,不要在 then() 方法里面定义 reject 回调函数,总是使用 catch() 方法。

  • catch() 方法返回的还是一个 Promise 对象,因此后面还可以接着调用 then() 或 catch() 方法。

finally()

finally() 方法用于指定一个在 Promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。

finally() 方法与 then(onFinally, onFinally) 类似,它们不同的是:

  • 不需要多次声明该函数或为该函数创建一个变量。
  • 由于无法知道 promise 的最终状态,所以 finally 的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
  • finally() 在回调正常完成时会把原来的兑现值或拒绝原因原样往下传。若回调抛错或返回拒绝的 Promise,则会覆盖原先的结果。
Promise.resolve(2).then(() => {}, () => {}) // resolved 的结果为 undefined
Promise.resolve(2).finally(() => {})        // resolved 的结果为 2
Promise.reject(3).then(() => {}, () => {})  // rejected 的结果为 undefined
Promise.reject(3).finally(() => {})         // rejected 的结果为 3

finally() 方法的实现:

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

Promise 拒绝事件

当 Promise 被拒绝时,会有以下两个事件之一被派发到全局作用域。

rejectionhandled

当 Promise 被拒绝、并且在 reject 函数处理该 rejection 之后会触发此事件。

注意: 只有当一个 Promise 错误最初未被处理,稍后又得到了处理,才会触发。

// 不会触发
window.addEventListener("rejectionhandled", event => {
    console.log("rejected: " + event.reason)
}, false)
const p = new Promise((resolve, reject) => {
    x = x + 1
}).then(res => { }, err => { })
// 触发
window.addEventListener("rejectionhandled", event => {
    console.log("rejected: " + event.reason)
}, false)
const p = new Promise((resolve, reject) => {
    x = x + 1
})
setTimeout(() => {
    p.then(res => { }, err => { })
}, 0)

unhandledrejection

当 Promise 被 reject 且没有 reject 处理器的时候,会触发 unhandledrejection 事件。

// window
window.addEventListener("unhandledrejection", event => {
    console.log(event.type)
}, false)
new Promise((resolve, reject) => {
    x = x + 1
}).then(() => { })
//Node.js
process.on('unhandledRejection', (reason, promise) => {})

Promise 串行运行

以下是用 Promise 来包裹 setTimeout() 函数,并返回 promise 实例,作测试用例:

const f1 = () => new Promise(resolve => {
    setTimeout(() => {
        console.log('f1')
        resolve('f1')
    }, 2000)
})
const f2 = () => new Promise(resolve => {
    setTimeout(() => {
        console.log('f2')
        resolve('f2')
    }, 1000)
})
const f3 = () => new Promise(resolve => {
    setTimeout(() => {
        console.log('f3')
        resolve('f3')
    }, 1000)
})

Promise.all() 和 Promise.race() 是并行运行参数中的 Promise 实例的两个函数,即,Promise 实例是并行执行的,不存在等待关系。

Promise.all([f2(), f1(), f3()]).then(res => {
    console.log('Promise.all:', res)
})
// f2
// f3
// f1
// Promise.all: (3) ['f2', 'f1', 'f3']

查看DEMO

Promise 串行是指每一个由 promise 封装的任务都顺序执行,即上一个执行完成后再执行下一个。

注意: 以下串行运行方法中的参数都是函数,不是 Promise 对象。

Promise 链式调用

Promise.resolve().then(f1).then(f2).then(f3).then(res => {
    console.log('参数:', res)
})
// f1
// f2
// f3
// 参数: f3

查看DEMO

for 版本

function sequencePromisesWithForThen (promises) {
    function recordValue(results, value) {
        results.push(value)
        return results
    }
    // 记录每个Promise实例的执行结果。
    // 如果不调用此方法,函数最后的Promise的传递出来的参数,只有最后一个Promise实例的执行结果
    const pushValue = recordValue.bind(null, [])
    let promise = Promise.resolve()
    for (let i = 0; i < promises.length; i++) {
        promise = promise.then(promises[i]).then(pushValue)
    }
    return promise
}
sequencePromisesWithForThen([f1, f2, f3]).then(res => {
    console.log('sequencePromisesWithForThen:', res)
})
// f1
// f2
// f3
// sequencePromisesWithForThen: (3) ['f1', 'f2', 'f3']

查看DEMO

reduce 版本

function sequencePromisesWithReduce (promises) {
    function recordValue(results, value) {
        results.push(value)
        return results
    }
    // 记录每个Promise实例的执行结果。
    // 如果不调用此方法,函数最后的Promise的传递出来的参数,只有最后一个Promise实例的执行结果
    const pushValue = recordValue.bind(null, [])
    return promises.reduce((promise, task) => {
        return promise.then(task).then(pushValue)
    }, Promise.resolve())
}
sequencePromisesWithReduce([f1, f2, f3]).then(res => {
    console.log('sequencePromisesWithReduce:', res)
})
// f1
// f2
// f3
// sequencePromisesWithReduce: (3) ['f1', 'f2', 'f3']

查看DEMO

async/await

async function sequencePromisesForAsync(promises) {
    const result = []
    for (let f of promises) {
        result.push(await f())
    }
    console.log('sequencePromisesForAsync:', result)
    return result
}
sequencePromisesForAsync([f1, f2, f3])
// f1
// f2
// f3
// sequencePromisesForAsync: (3) ['f1', 'f2', 'f3']

查看DEMO

Promise 示例讲解

Promise 嵌套示例一

嵌套 Promise 是一种可以限制 catch 语句的作用域的控制结构写法。明确来说,嵌套的 catch 仅捕捉在其之前同时还必须是其作用域的异常,而捕捉不到在其链式以外或者其嵌套域以外的异常。

注意: 简便的 Promise 链式编程最好保持扁平化,尽量不要嵌套 Promise。

Promise 嵌套中,优先执行 Promise 构造器里的同步任务,再执行 then 回调。

new Promise((resolve, reject) => {
    console.log('1')
    resolve()
    new Promise((resolve, reject) => {
        console.log('2') // c2
        resolve()
    }).then(() => {
        console.log('3') // c3
    })
}).then(() => {
    console.log('4')
})
// 1
// 2
// 3
// 4

注意: Promise 函数内的代码是同步执行的,then 函数内的才是微任务,即异步的代码。

Promise 嵌套示例二

关键要点:

  • new Promise(executor) 内部代码同步执行;仅 .then 回调进入微任务队列;
  • .then 不写 return,默认返回 undefined,下一级 .then 直接追加队列尾部;
  • 队列严格先进先出;执行过程新增任务只能排到队尾,不能插队;
  • .then 内部新建 Promise,会生成独立子链;外层链式不受内部子链阻塞,多条链的微任务混合排队。
new Promise((resolve) => {
    console.log(1)
    resolve()
})
.then(() => { // t1
    new Promise(resolve => {
        console.log(2)
        resolve()
    })
    .then(() => { // t2
        console.log(3)
    })
    .then(() => { // t4
        console.log(6)
    })
})
.then(() => { // t3
    console.log(4)
    new Promise((resolve) => {
        console.log(5)
        resolve()
    })
    .then(() => { // t5
        console.log(7)
        new Promise((resolve) => {
            console.log(8)
            resolve()
        })
        .then(() => { // t7
            console.log(10)
        })
    })
    .then(() => { // t8
        console.log(11)
    })
})
.then(() => { // t6
    console.log(9)
})

// 输出顺序:1 2 3 4 5 6 7 8 9 10 11

核心理解:一个 .then 一个微任务;当前 .then 完成,才会去执行下一个 .then

步骤拆解:

  1. 同步执行打印 1t1 入队;队列:[ t1 ]
  2. 执行 t1:打印 2;内部第一个 .then(t2)、同级下一个 .then(t3) 入队;队列:[ t2, t3 ]
  3. 执行 t2:打印 3;下一级 t4 入队;队列:[ t3, t4 ]
  4. 执行 t3:打印 4;同步执行内部 Promise,打印 5;内部 t5、同级 t6 入队;队列:[ t4, t5, t6 ]
  5. 执行 t4:打印 6;队列:[ t5, t6 ]
  6. 执行 t5:打印 7;同步执行内部 Promise,打印 8;内部 t7t8 入队;队列:[ t6, t7, t8 ]
  7. 执行 t6:打印 9;队列:[ t7, t8 ]
  8. 执行 t7:打印 10;队列:[ t8 ]
  9. 执行 t8:打印 11;队列清空。

参考链接

MDN - promise

MDN - 使用 Promise

阮一峰 ECMAScript 6 入门 promise

© lizhao all right reserved,powered by Gitbook文件修订时间: 2026-08-31 22:08:23

results matching ""

    No results matching ""