阅读 120

promise-异步编程利器

文章通过实现一个简易的类来理解Promise原理,并将Promise和传统回调对比,展示了Promise在异步编程中的优势,最后是介绍了Promise在实际开发中的应用

一. 概念

Promise将异步操作回调进行解耦,并通过执行状态将两者关联。异步操作结束后把状态通知Promise,由Promise负责触发回调函数。

二. Promise原理

1. 状态变更

输入:

let p0 = new Promise((resolve, reject) => {})
console.log('p0', p0)

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
})
console.log('p1', p1)

let p2 = new Promise((resolve, reject) => {
    reject('失败')
})
console.log('p2', p2)

let p3 = new Promise((resolve, reject) => {
    throw('报错')
})
console.log('p3', p3)复制代码

输出:

image-20220102102219777

结论:

  1. 刚创建的Promise对象处于pending状态。

  2. 当异步操作完成后,调用resolve/reject变更状态为fulfilled/rejected。

  3. 执行函数发生异常(同步异常)时,变更状态为rejected。

image-20220102193706740

实现:

class MyPromise {
    constructor(executor) {

        this.initValue();
        // 由于resolve/reject是外部函数executor调用的,所以必须将this硬绑定为当前MyPromise对象
        this.initBind();
        
        try {
            // 执行传进来的函数
            executor(this.resolve, this.reject);
        } catch (e) {
            // 捕捉到错误直接执行reject
            this.reject(e);
        }
    }

    initBind() {
        this.resolve = this.resolve.bind(this);
        this.reject = this.reject.bind(this);
    }

    initValue() {
        this.PromiseResult = null;
        this.PromiseState = 'pending';
    }

    resolve(value) {
        // 状态只能由pending转换为fulfilled/rejected
        if (this.PromiseState == 'pending'){
            this.PromiseState = 'fulfilled';
            this.PromiseResult = value;
        }
    }

    reject(reason) {
         // 状态只能由pending转换为fulfilled/rejected
        if (this.PromiseState !== 'pending'){
            this.PromiseState = 'rejected';
            this.PromiseResult = reason;   
        }
    }
}复制代码

2. 执行回调

输入/输出:

// 立刻输出 ”resolve=成功“
const p1 = new Promise((resolve, reject) => {
    resolve('成功');
}).then(res => console.log('resolve=',res), err => console.log('reject=',err))

// 1秒后输出 ”reject=失败“
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('失败');
    }, 1000)
}).then(res => console.log('resolve=',res), err => console.log('reject=',err))复制代码

结论:

  • then接收两个参数:成功回调失败回调

  • 当Promise状态为fulfilled执行成功回调,为rejected执行失败回调

  • 通过多次调用then可以注册多个回调函数,注意区分链式调用

实现:

通过then注册回调时,如果Promise状态为fulfilled/rejected,则执行回调函数;

如果Promise状态为pending,则先保存回调函数,等异步操作结束后再执行回调。

initValue() {
    ...
    // 保存回调函数。
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];
}

resolve(value) {
    ...
    // 状态变更为fulfilled,执行保存的成功回调
    while (this.onFulfilledCallbacks.length) {
        this.onFulfilledCallbacks.shift()(this.PromiseResult);
    }
}
reject(reason) {
    ...
    // 状态变更为rejected,执行保存的失败回调
     while (this.onRejectedCallbacks.length) {
         this.onRejectedCallbacks.shift()(this.PromiseResult);
     }
}

then(onFulfilled, onRejected) {
    // 参数校验,确保一定是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    if (this.PromiseState === 'fulfilled') {
        // 执行fulfilled回调
        onFulfilled(this.PromiseResult);
    } else if (this.PromiseState === 'rejected') {
        // 执行rejected回调
        onRejected(this.PromiseResult);
    }else if (this.PromiseState === 'pending') {
        // Promise为pending状态,暂时保存两个回调
       this.onFulfilledCallbacks.push(onFulfilled);
       this.onRejectedCallbacks.push(onRejected);
    }
}复制代码

3. 链式调用

输入/输出

// 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {
    resolve(100);
}).then(res => 2 * res)
  .then(res => console.log(res))

// 链式调用 输出300
const p4 = new Promise((resolve, reject) => {
    resolve(100);
}).then(res => new Promise((resolve, reject) => resolve(3 * res)))
  .then(res => console.log(res))复制代码

结论

  1. then方法本身会返回一个新Promise对象。

  1. 如果回调函数返回值是Promise对象,则新Promise对象状态由该Promise对象决定。

  1. 如果回调函数返回值不是Promise对象,则新Promise对象状态为成功。

实现

 then(onFulfilled, onRejected) {
     ...
     var thenPromise = new MyPromise((resolve, reject) => {
         const resolvePromise = cb => {
             try {
                 const x = cb(this.PromiseResult)
                     if(x === thenPromise){
                         // 自己等待自己完成,循环等待:在then回调中返回了then的返回值。
                         reject(new TypeError('Chaining cycle detected for promise'));
                     }
                     if (x instanceof MyPromise) {
                         // 如果返回值是Promise对象,则新Promise状态由该Promise决定。
                         x.then(resolve, reject);
                    } else {
                        // 非Promise就直接成功
                        resolve(x);
                    }
                } catch (err) {
                    // 处理报错
                    reject(err);
                }
            }

            if (this.PromiseState === 'fulfilled') {
                // 如果当前为成功状态,执行第一个回调
                resolvePromise(onFulfilled);
            } else if (this.PromiseState === 'rejected') {
                // 如果当前为失败状态,执行第二个回调
                resolvePromise(onRejected);
            } else if (this.PromiseState === 'pending') {
                // 如果状态为待定状态,暂时保存两个回调
                this.onFulfilledCallback = resolvePromise(onFulfilled);
                this.onRejectedCallback = resolvePromise(onRejected);
            }
        })

        // 返回这个包装的Promise
        return thenPromise;

    }复制代码

4. 调用时序

输入输出

setTimeout(()=>{console.log(0)},0);
const p = new Promise((resolve, reject) => {
    console.log(1);
    resolve()
}).then(() => console.log(2))

console.log(3)

// 输出顺序是 1 3 2 0复制代码

结论

  1. 即使Promise的状态被立刻更新为fulfilled,回调函数也不会被立刻执行。

  2. 回调函数是在事件循环的微任务中执行的。

  3. 因为回调本来就是异步的,放在微任务执行可以让后面的同步代码尽快被执行。

实现

const resolvePromise = cb => {
    setTimeout(() => {
       // 执行回调...
    })
}复制代码

三. Promise VS 传统回调

1. 回调地狱

面对执行多重异步操作的场景,传统回调需要将回调函数作为参数传入,存在“回调地狱”问题。而Promise将异步操作和回调函数解耦了,不需要在一开始就传入回调函数。

// 传统回调 实现多重异步操作
doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);
// Promise 实现多重异步操作
doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);复制代码

2. 异常捕获

注:这里的异常是指 回调函数异常,而不是异步操作异常。

// 下面是一个典型的超时回调,由于timeoutCallback是在异步操作中被调用的,所以try catch无法捕获异步异常。
try{
    setTimeout(()=>timeoutCallback("3 seconds passed"), 3000);
}catch(err){
    // 这里是无法捕获到 timeoutCallback异常的。
    console.log(err);
}

// Promise负责调用回调函数。当异步操作结束Promise状态变更后,Promise会在调用回调函数时加上try catch。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(3000).then(() => timeoutCallback("3 seconds passed")).catch(err=>{console.log(err)});复制代码

四. Promise应用

1. 创建已完成的Promise

Promise.resolve() 和 Promise.reject() 
复制代码

2. 封装传统回调

由于传统回调存在 回调地狱缺乏异步异常捕获等问题,所以使用Promise对已存在的传统回调进行封装。

// 下面是一个典型的超时回调,由于timeoutCallback是异步执行的,所以无法捕获异步回调中的异常。
try{
    setTimeout(()=>timeoutCallback("3 seconds passed"), 3000);
}catch(err){
    // 这里是无法捕获到 timeoutCallback异常的。
    console.log(err);
}

// Promise可以将异步操作和回调分开,当异步操作完成后调用回调时,会自动在回调函数加上try catch。
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(3000).then(() => timeoutCallback("3 seconds passed")).catch(err=>{console.log(err)});复制代码

3. 执行多个异步操作

// 并行多个异步操作,然后等所有操作完成后进入下一步操作
Promise.all([promise1, promise2, promise3])
.then(([result1, result2, result3]) => { 
    // next step 
});
.catch(err){
    // 当任意一个操作reject时,进入catch,并返回对应reject信息。
}

// 按顺序执行多个异步操作(链式调用)
[promise1, promise2, promise3].reduce((pre, cur) => pre.then(()=>{return cur}), Promise.resolve())
.then(result3 => {
    /* use result3 */ 
}).catch(err=>{
    // 当任意一个操作reject时,进入catch,并返回对应reject信息。
});
// 在es2017中,也可以使用async/await对上面代码进行优化
let result;
try{
    for (const pro of [promise1, promise1, promise1]) {
        result = await pro(result);
    }
}catch(err){
    result = err;
}


作者:Ys561
链接:https://juejin.cn/post/7048570128249651231

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐