彻底理解Promise(下)---试题篇

1,025 阅读3分钟
  • 上篇讲到Promise的相关实现,本篇文章着重于讲解Promise相关的试题

Promise状态凝固

const promise = new Promise((resolve, reject) => {
    resolve('success1');
    reject('error');
    resolve('success2');
});

promise.then((res) => {
    console.log('then:', res);
}).catch((err) => {
    console.log('catch:', err);
})

promise的状态是不可逆的,一旦发生改变,便不会再变化,所以上述输出: 输出: 'then:' success1

Promise值穿透

Promise.resolve(1)
    .then(2)
    .then(Promise.resolve(3))
    .then(console.log)

then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会穿透下面。在上篇的代码中有体现

promise执行顺序

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() =>{
    console.log(6);
})

在原生的Promise中Promise.resolve()会产生两次微任务,所以上述代码中第一个Promise.resolve()可以变成以下代码

Promise.resolve().then(() => {
    console.log(0);
    return 4;
})
.then()
.then()
.then((res) => {
    console.log(res)
})

在js引擎中为了让microtask尽快的输出,做了一些优化,连续的多个then如果没有reject或者resolve会交替执行then而不至于让一个堵太久完成用户无响应,所以上面的代码输出

  • 0123456

Promise与async结合

async function async1 (){
    console.log(1)
    let result = await async2()
    console.log(3)
}
async function async2 (){
    console.log(2)
}
setTimeout(()=> {
    console.log(5)
})
// 注意这里异步的执行顺序
Promise.resolve().then(res => {
    console.log(7)
})
async1()
Promise.resolve().then(res => {
    console.log(4)
})
console.log(6)
  • 输出:1267345 await前面的代码是同步执行的,后面的代码相当于then的链式调用,会被添加到事件队列里,输出上述结果

Promise串形化

  • reduce实现
const mergePromise = ajaxArray => {
    let data = []  // 收集执行完的结果
    return ajaxArray.reduce((pre, next)=> {
        return pre.then(next).then((res)=> {
            data.push(res)
            return data
        })
    }, Promise.resolve())
};
mergePromise([ajax1,ajax2,ajax3]).then(res => {
    console.log('最终按顺序输出结果为:', res)
})
  • 模拟队列方式
function  run () {
    let p = promises.shift()
    if (!p) return
    return  p().then(res=> {
        run()
    })
}
run()

Promise控制并发

实现原理,构建并发长度的数组,使用promise.race获取最先从并发数组中完成的请求,然后将其从并发数组中删除,并放入新的请求,依次循环

  • 函数实现
const request = (url) => {
    let second = Math.ceil(Math.random()*5*1000)

    return new Promise((resolve, reject)=> {
        setTimeout(()=> {
            console.log(second+'秒请求完成:', url)
            resolve(url)
        }, second)
    }).then(res => {
        console.log('请求完成成后的代码')
        return res
    })
}
const arr = [
    'bytedance.com',
    'tencent.com',
    'alibaba.com',
    'microsoft.com',
    'apple.com',
    'hulu.com',
    'amazon.com'
]

function limitLoad(urls, handler, limit) {
    return new Promise((resolve, reject) => {
        const sequence = [].concat(urls)
        let pool = []
        let result = [] // 汇总最终所有结果
        pool = sequence.splice(0, limit).map((url, index) => {
            return handler(url).then(()=>{
                result.push({url, index})
                resolve(result)
                return index
            })
        })
        let race = Promise.race(pool)

        for (let i= 0; i< sequence.length; i++) {
            race = race.then(index => {
                pool[index] = handler(sequence[i]).then(()=>{
                    result.push({url: sequence[i], index})
                    resolve(result)
                    return index
                })
                return Promise.race(pool)
            })
        }
    })
}
limitLoad(arr, request, 2).then(res=>console.log(res))
  • 类实现
//promise并发限制
class PromisePool {
    constructor(max, fn) {
        this.max = max; //最大并发量
        this.fn = fn; //自定义的请求函数
        this.pool = []; //并发池
        this.urls = []; //剩余的请求地址
    }
    start(urls) {
        this.urls = urls; //先循环把并发池塞满
        while (this.pool.length < this.max) {
            let url = this.urls.shift();
            this.setTask(url);
        }
        //利用Promise.race方法来获得并发池中某任务完成的信号
        let race = Promise.race(this.pool);
        return this.run(race);
    }
    run(race) {
        race
            .then(res => {
                //每当并发池跑完一个任务,就再塞入一个任务
                let url = this.urls.shift();
                this.setTask(url);
                return this.run(Promise.race(this.pool));
            })
    }
    setTask(url) {
        if (!url) return
        let task = this.fn(url);
        this.pool.push(task); //将该任务推入pool并发池中
        console.log(`\x1B[43m ${url} 开始,当前并发数:${this.pool.length}`)
        task.then(res => {
            //请求结束后将该Promise任务从并发池中移除
            this.pool.splice(this.pool.indexOf(task), 1);
            console.log(`\x1B[43m ${url} 结束,当前并发数:${this.pool.length}`);
        })
    }
}
//test
const URLS = [
    'bytedance.com',
    'tencent.com',
    'alibaba.com',
    'microsoft.com',
    'apple.com',
    'hulu.com',
    'amazon.com'
]
//自定义请求函数
const requestFn = url => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(`任务${url}完成`)
        }, 1000)
    }).then(res => {
        console.log('外部逻辑', res);
    })
}
const pool = new PromisePool(5, requestFn); //并发数为5
pool.start(URLS)

Promise实现红绿灯

//1、实现三秒红灯,两秒绿灯,1秒黄灯
function red() {
    console.log('red');
}
function green() {
    console.log('green');
}
function yellow() {
    console.log('yellow');
}

const light = function (timer, cb) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            cb();
            resolve();
        }, timer);
    });
};

const step = async function () {
    // promise链式调用版
    // Promise.resolve().then(function (resolve) {
    //    return light(3000, red);
    // }).then(function () {
    //    return light(2000, green);
    // }).then(function () {
    //   return  light(1000, yellow);
    // }).then(function () {
    //     step();
    // });
    // async/await版
    while (1){
        await light(3000, red);
        await light(2000, green);
        await light(1000, yellow);
    }

}
step();

以上就是我收集的一些关于Promise的试题,基本上可以覆盖现在市面上的大部分面试题,

  • 写作不易,欢迎点赞