关于Promise的总结:使用Promise,告别回调!

443 阅读7分钟

看过大概十几集关于promise的视频,也搜索过掘金里面的很多文章。但是总觉得对于promise一知半解,而且无法形成知识体系,网上资料知识点过于杂乱。所以这里分享一下我对promise的理解。

一、PromiseA+规范

首先是PromiseA+规范,即是一个规则,也是一种标准。

记得刚开始看Promise,看着网上各种各样的文章一头雾水,于是沉下心来想看PromiseA+规范。 想看看一个标准的Promise应该有怎么样的要求。于是通读了翻译成中文的PromiseA+规范。

这里给出原文以及我看的译文地址,以便大家学习:

PromiseA+规范:promisesaplus.com/

PromiseA+规范译文:malcolmyu.github.io/malnote/201…

github:github.com/promises-ap…

规范中有这么一句话:

一个开放、健全且通用的 JavaScript Promise 标准。由开发者制定,供开发者参考。

我觉得这句话是非常重要的,我们不需要一个严苛一成不变的标准,在学习Promise的时候我们 更应该思考的是为什么开发者设计出来出来这种标准,怎么样有利于我们的开发。又是怎么实现它的便捷性的。

1、首先什么是promise

promise表示一个异步操作的结果,与之相配的是then方法,该方法注册了两个回调函数分别是resolve和reject。又或者说 promise 是一个拥有 then 方法的对象或函数。

2、解决(fullfill/ resolve)

指一个promise成功时进行的的一系列操作。

3、拒绝(reject)

指一个promise失败时进行的一系列操作。

4、终值(value)

指promise被解决时传递给解决回调的值

5、原因(reason):

指promise被拒绝时传递给拒绝回调的值

6、thenable

是一个定义了then方法的对象或者函数

7、值value

指任何JavaScript的合法值,包括 undefined , thenable 和 promise

8、异常exception

是使用 throw 语句抛出的一个值

9、拒因reason

表示一个 promise 的拒绝原因。

10、promise的状态

(1)等待态pending,翻译过来就是【未决定的】,也就是说当前promise的状态还没有被改变,promise还没有成功或者失败,此时可以迁移至执行态或拒绝态。

(2)执行态fullfilled/resolved,此时promise已经成功而且拥有不可变的终值value

(3)拒绝态rejected,此时promise已经失败而且拥有不可变的原因reason

11、then方法

(1)then方法就是提供来访问promise状态的

这个then方法接受两个参数,分别是onFulfuilled和onRejected

promise.then(onFulfilled, onRejected)

(2)then方法可以被多次调用比如

const p = new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(2)

        }, 1000);
    }).then(
        value => {},
        reason => {}
    ).then(
        value => {},
        reason => {}
    ).catch(
        reason => {}
    )

(3) then的返回

then方法必须返回一个promise对象

注意:比如我们在下面这段代码中,不论 promise1 被 reject 还是被 resolve 时 它的返回值 都会被 resolve,只有出现异常时才会被 rejected。

const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject(2)
    }, 1000);
}).then(
    value => {
        console.log("value1 " + value);
    },
    reason => {
        console.log("reason1 " + reason);
    }
).then(
    value => {
        console.log("value2 " + value);
    },
    reason => {
        console.log("reason2 " + reason);
    }
).catch(
    reason => {
        console.log("catch");
    }
)

二、为什么要用promsie?

1、promise是Js异步编程的一种解决方案。已经被ES6纳入规范中。

2、指定回调函数的方式更加灵活

我们知道,旧的回调函数必须在启动异步任务前指定,

而现在的promise的过程是这样的:

启动异步任务 => 返回promise对象 => 给promise对象绑定回调函数(甚至可以在异步之后)

3、支持链式调用,可以解决回调地狱问题

(1)什么是回调地狱?回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。说白了就是不停的回调嵌套,导致整个代码非常的不优雅。

(2)回调地狱的缺点? 不便于阅读 / 不便于异常处理,代码臃肿、可读性差、耦合度过高、代码复用性差

(3)解决方案? promise链式调用(终极解决方案? async / await)

三、promise的基本使用

我们从上面可以看到,promise是Js异步编程的一种解决方案,支持链式调用,解决回调地狱。 接下来就看看他的基本使用。

// 1、创建一个新的promise对象
    const p = new Promise((resolve, reject) => { // 执行器函数,执行异步操作  同步回调
        // 2、执行异步操作任务
        setTimeout(() => {
            const time = Date.now()
            if (time % 2 == 0) {
                // 3.1 如果成功,调用resolve(value)
                resolve('成功的数据' + time)
            } else {
                // 3.2 如果失败,调用reject(reason)
                reject('失败的数据' + time)
            }
        }, 1000);
    })

  p.then(
        value => { // 接收得到成功的value数据  onResolved
            console.log('成功的回调', value)
        },
        reason => { // 接受得到失败的reason数据  onRejected
            console.log('失败的回调', reason)
        }
    )

除此之外,我们平时也可以这样使用:

 function usePromise(id) {
        return new Promise((resolve, reject) => {
            if (id == 1) {
                console.log(1)
                resolve(1)
            } else {
                console.log(false)
                reject(2)
            }
        })
    }
    usePromise(1).then( 
        id => {
            console.log(id);
        }
    )

另外,这里列出promise常用的API,也是后面的手写Promise的依据

Promise.resolve(value)  --类方法,返回的状态为resolved
Promise.reject(reason)  --类方法,返回的状态为rejected
Promise.prototype.then  --实例方法,注册回调函数
Promise.prototype.catch --实例方法,捕获异常
Promise.race  -- 类方法,返回最先执行的promise任务结果
Promise.all   -- 类方法,返回所有任务成功的结果或者其中一个任务失败的结果

四、关于宏任务和微任务

此处要提到宏队列和微队列,因为我们在理解Promise的then执行顺序的时候需要了解到这两个概念。 我这里就简单的解释一下,这两个概念还涉及到JavaScript V8引擎,这里也不多做详细解释。

1、宏任务:

为了协调这些任务有条不紊地在主线程上执行,页面进程引入了消息队列和事件循环机制,渲染进程内部会维护多个消息队列,比如延迟执行队列和普通的消息队列。然后主线程采用一个 for 循环,不断地从这些任务队列中取出任务并执行任务。我们把这些消息队列中的任务称为宏任务。

2、微任务:

微任务就是一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前。

我们常见的宏队列任务有:dom事件回调、ajax回调、定时器回调 常见的微队列任务有:promise回调、mutation回调

 setTimeout(() => { // 会立即放到宏队列
            console.log('timeout callback');
        }, 0);
        setTimeout(() => { // 会立即放到宏队列
            console.log('timeout callback');
        }, 0);
        Promise.resolve(1).then(
            value => { // 会立即放入微队列
                console.log('Promise onResolved', value)
            }
        )
        Promise.resolve(2).then(
            value => { // 会立即放入微队列
                console.log('Promise onResolved', value)
            }
        )

五、promise练习题

接下来给出几道比较简单的关于promise执行顺序的题,结合宏队列和微队列以及异步操作,可以很容易的做出来。我就不给出具体的解析了,如果有不懂的可以在评论问或者查资料~~

1、写出这段代码执行的结果:

setTimeout(() => { // 宏队列异步
   console.log(1);
}, 0);
Promise.resolve().then(() => { // 微队列异步
   console.log(2);
})
Promise.resolve().then(() => { // 微队列异步
   console.log(3);
})
console.log(4); // 同步

执行结果:4 2 3 1

2、写出这段代码执行的结果:

setTimeout(() => { // 宏队列异步
      console.log(1);
}, 0);
new Promise((resolve) => {
      console.log(2);
      resolve()
}).then(() => {
      console.log(3);
}).then(() => {
      console.log(4);
})
console.log(5);

执行结果:2 5 3 4 1

3、写出这段代码执行的结果:

const first = () => (new Promise((resolve, reject) => {
    console.log(3);
    let p = new Promise((resolve, reject) => {
        console.log(7);
        setTimeout(() => {
            console.log(5);
            resolve(6) // 因为只改变一次,所以这个resolve无效
        }, 0);
        resolve(1)
    })
    resolve(2)
    p.then((arg) => {
        console.log(arg); //1
    })
}))
first().then((arg) => {
    console.log(arg); //2
})
console.log(4);

执行结果:3 7 4 1 2 5

4、写出这段代码执行的结果:

setTimeout(() => {
    console.log(0);
}, 0);
new Promise((resolve, reject) => {
    console.log(1);
    resolve()
}).then(() => {
    console.log(2);
    new Promise((resolve, reject) => {
        console.log(3);
        resolve()
    }).then(() => {
        console.log(4);
    }).then(() => {
        console.log(5);
    })
}).then(() => {
    console.log(6);
})
new Promise((resolve, reject) => {
    console.log(7);
    resolve()
}).then(() => {
    console.log(8);
})

执行结果:1 7 2 3 8 4 6 5 0

另外,关于比较重要的手写promise,我还在想怎么写比较方便记忆并且便捷使用,希望能尽快搞懂它~~