Promise对象

专业英语:

单词 释义
promise n. 许诺,允诺;希望
pending adj. 未决定的;行将发生的
resolve vi. 解决;决心;分解
reject vt. 拒绝;排斥;抵制;丢弃

Promise概述

  • Promise主要是用来处理异步任务的。
  • 在JS中,promise的含义是“期约”。
  • ECMAScript 6中全局环境中新增了引用类型Promise,可以通过new操作符来实例化。创建新期约时需要传入执行器(executor)函数作为参数。
const promise = new Promise(function(resolve, reject) {
    // code
})
  • 期约对象有两个内部属性,其中[[PromiseState]]表示当前状态,[[PromiseResult]]表示执行结果

Promise对象

  • 期约是一个有状态的对象,可能处于如下三种状态之一:

    • 待定(pending):简单的说,在异步操作中,这就表示操作还未结束;
    • 兑现(fulfilled/resolved):在异步操作中,这表示操作已经结束,且结果为成功;
    • 拒绝(rejected):在异步操作中,这表示操作已经结束,但结果为失败;
  • 待定状态可以转化为兑现状态或者拒绝状态,但这个过程是不可逆的,转换过程由用户传入的执行器控制:

new Promise(function(resolve, reject) {
    // do something
    resolve('hello world');	// 主动调用resolve,将状态转化为兑现(此时它接收一个参数作为执行结果)
})
new Promise(function(resolve, reject) {
    // do something
    reject('hello world');	// 主动调用reject,将状态转化为拒绝(此时它接收一个参数作为执行结果)
})
  • 注意转换是不可逆的,也即转换为兑现或者拒绝后状态就无法再改变:
new Promise(function(resolve, reject) {
    // do something
    resolve();	// 主动调用resolve,将状态转化为兑现
    reject();	// 因为状态已经变成了兑现,所以这里的操作会被静默拒绝(忽略)。
})
  • 状态的转换只能由执行器函数决定,如果我们不主动调用resolve或者reject,那么状态将一直保持为pending

Promise.prototype.then()

  • Promise.prototype.then()是为了给期约实例添加处理程序。then方法最多接收两个函数作为参数:(传给then的任何非函数参数都会被忽略)
new Promise(() => {}).then(onResolved, onRejected);
  • onResolved也即期约转化为兑现时的回调函数onRejected期约转化为拒绝的回调函数。这两个函数都会被传入一个值,那就是期约的执行结果。
  • 当执行器主动调用resolve转化期约状态后,onResolved就会作为回调函数被调用(注意这里resolve不等于onResolved,他们是不同的)。
new Promise(function(resolve, reject) {
    // do something
    resolve('hello world');	// 主动调用resolve,将状态转化为兑现
    console.log('测试点1');
}).then(result => {
    console.log(result);
})

/* 控制台输出:
测试点1
hello wolrd
*/
  • 当执行器主动调用reject转化期约状态后,onRejected就会作为回调函数被调用:
new Promise(function(resolve, reject) {
    // do something
    reject('hello world');	// 主动调用reject,将状态转化为兑现
    console.log('测试点1');
}).then(null, result => {
    console.log(result);
})

/* 控制台输出:
测试点1
hello wolrd
*/
  • then()函数不需要Promise实例化后立即调用,可以在任何时候调用then传递处理程序,而如果在你调用then传递参数时,Promise状态已经发生了转化,有了执行结果,那么你传递的处理程序会立即被执行(这很重要);另外,你也可以多次调用then:
const promise = new Promise(function(resolve, reject) {
    // do something
    resolve('hello world');	// 主动调用reject,将状态转化为兑现
})
setTimeout(function () {
    promise.then(result => console.log('测试点1', result))
    promise.then(result => console.log('测试点2', result))
}, 1000)
/*
测试点1 hello world
测试点2 hello world
*/
  • 此外then()函数也是有返回值的,它会返回一个新的Promise实例,该实例具有如下特征:
    1. 当原实例状态为pending时,这个新实例的状态也会保持为pending
    2. 当原实例状态发生了转化,但我们没有设置对应状态的处理程序(如果状态为resolved,但我们只设置了onRejected,那么也算是未设置处理程序),那么这个新的实例就会使用Promise.resolve()(后面会详细说明)基于原有实例的执行结果重构当前状态。
    3. 在第二种情况下,如果存在对应状态的处理程序,那么新实例就会继续保持pending状态,等待处理程序执行完毕并返回结果,然后使用Promise.resolve()基于该返回值构建新实例。(实际上我也没太弄懂参考书上的描述,结果有点怪异,但从测试结果来看大概是这样的)
    4. 如果原有的Promise实例在执行过程中发送了错误(如状态转化为reject但是我们没有设置onRejected处理函数),那么新实例的状态就会变为rejected,执行结果就是原本实例的执行结果。
console.log(
    new Promise((resolve, reject) => resolve('resolve')).then(),
    new Promise((resolve, reject) => resolve('resolve')).then(result => 'then ' + result),
    new Promise((resolve, reject) => resolve('resolve')).then(undefined, result => 'then ' + result)
);
console.log(
    new Promise((resolve, reject) => reject('reject')).then(),
    new Promise((resolve, reject) => reject('reject')).then(result => 'then ' + result),
    new Promise((resolve, reject) => reject('reject')).then(undefined, result => 'then ' + result)
);

Promise对象

  • Promise的链式调用与传值现象:利用then的返回值特性,我们可以对Promise对象进行链式调用,并且这其中存在有趣的传值现象
    • 因为then()方法总是会返回一个新的Promise实例对象,而每个实例对象都可以调用then()方法,于是我们可以写成promise.then().then().then()......
    • 根据then()返回值的特点我们可以知道,如果我们没在then()中添加对应状态的处理程序,那么新生成的Promise对象就会继承原来的Promise实例对象的执行结果,这样就会形成层层向后传递的现象,直到某个then()中添加了对应状态的处理程序
new Promise(resolve => resolve('hello')).then().then(result => console.log(result));
// hello
new Promise((resolve, reject) => reject('hello')).then().then(undefined, result => console.log(result));
// hello
new Promise((resolve, reject) => reject('hello'))
    .then(result => console.log('test1', result))
    .then(undefined, result => console.log('test2', result));
// test2 hello
new Promise((resolve, reject) => resolve('hello'))
    .then(result => console.log('test1', result))
    .then(result => console.log('test2', result), result => console.log('test3', result));
// test1 hello
// test2 undefined

Promise.resolve()

  • 该方法是Promise对象上的方法,而不是原型上的方法,因此不会被继承。
  • resolve()方法接收一个参数,然后返回一个Promise实例对象。根据传入参数的不同,返回的Promise实例也会有所区别。
  1. 传入的参数不是对象类型:返回的实例对象状态为兑现(fulfilled/resolved),且执行结果就是我们传入的参数
  2. 传入的参数是一个普通对象:结果与第一种情况一样。
console.log(Promise.resolve('hello world'),
    Promise.resolve(undefined),
    Promise.resolve(null),
    Promise.resolve({test: 'hello'}));

Promise对象

  1. 传入的参数是Promise实例对象:原封不动的返回这个对象。
  2. 参数是一个含有then()方法的对象:将其中的then()方法作为执行器函数包装成Promise实例对象后返回。
const obj = {
    then(resolve, reject) {
        console.log('then函数被调用');
        resolve('hello world');
    }
}
Promise.resolve(obj).then(result => console.log('测试点', result));
/*
then函数被调用
测试点 hello world
*/

延迟执行 + 链式调用

  • 前不久面试了某大厂的前端实习岗位,其中有一道代码题是让我写出一个obj.print('start').sleep(3000).print('测试点1').sleep(3000).print('end')这样的链式调用的(不能使用队列的方式),且可以延迟执行的对象,当时试了半天也没写出来,于是痛并思痛,决定好好研究一下Promise,现在终于可以轻松搞定了,代码如下:
const timeOutPrint = {
    promise: Promise.resolve(),
    sleep(time) {
        this.promise = this.promise.then(() => new Promise(resolve => setTimeout(resolve, time)))
        return this;
    },
    print(msg) {
        this.promise = this.promise.then(() => console.log(msg));
        return this;
    }
}

timeOutPrint.print('start').sleep(3000).sleep(3000).print('测试点1').sleep(3000).print('end');
上一篇:前端Promise总结笔记


下一篇:《Promise学习笔记》- 4Promise自定义封装之封装构造器函数