Promise 相关面试题

606 阅读6分钟

promise相关

Promise是异步编程的一种解决规范,从语法上讲promise是一个对象,从它可以获取异步操作的消息;从本意来讲,它是承诺,承诺过一段时间给你一个结果。promise有三种状态pending(等待)fulfilled(成功)rejected(失败),状态一旦改变,就不会再变,创建promise示例后,会立即回调种的内容执行。

promise的用处:

  • 回调地狱,代码难维护,常常第一个的函数的输出是第二个函数的输入这种现象。
  • promise可以支持多个并发的请求,获取并发请求中的数据。

面试题

return Error

Promise.resolve()
    .then(res => {
        console.log('a');
        return new Error('error');
    })
    .then(res => {
        console.log('b');
        console.log('then: ', res);
    })
    .catch(res => {
        console.log('c');
        console.log('catch: ', res);
    })

输出结果:

  • a
  • b
  • then: Error error
    解析:then 中return 一个Error,并不会进入catch ,而是 fulfilled状态,会按then的逻辑往下走

then回调函数的返回

const promise = Promise.resolve()
    .then(res => {
        return promise;
    })

输出结果:执行这个段会报错,Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
解析:在then中返回起自身,会导致死循环

then的参数是函数

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

输出: 1
解析:then的参数是函数,第一个then 和 第二个then的参数都不是函数,即无效,所以最后输出结果是 1

then的第二个参数

Promise.resolve()
    .then(
        sucess => { throw new Error('error')},
        fail => { console.log('fail: ', fail) }
    )
    .catch(
        fail2 => { console.log('fail2: ', fail2) }
    )

输出:fail2: Error error
解析:then可以接受两个函数作为参数,一个为resolve时调用,一个rejected是调用,两个函数一定只有一个会被调用;catch中的函数,可以监控到前面(包括then)中的异常。

catch 和 then

Promise.reject('error')
    .then(res => {
        console.log('then1: ', res);
        throw new Error('erro1');
    })
    .catch(err => {
        console.log('catch1: ', err);
        return 'catch1'
    })
    .then(res => {
        console.log('then2: ', res);
        return new Error('then2: error');
    })
    .catch(err => {
        console.log('catch2: ', err)
    })

输出:

  • catch1: error
  • then2: catch1
    解析:第一个then是不会进入的,会进入第一个cath,执行里面的函数,然后进入第二个then,执行其中的语句,但是Error是return的,而不是throw,所以不会进入第二个catch

执行队列1

new Promise((resolve, reject) => {
    console.log(1);
    resolve();
    console.log(2);
})
    .then(() => {
        console.log(3);
    })
console.log(4);

输出: 1 2 4 3
解析:new Promise的回调函数回同步执行,所以会首先输出 1 2,resolve的需要 需要进入下一个事件循环,所以最后输出3;

执行队列2

const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 3000);
});

// 下面三种的区别
promise.then(() => {
    return Promose.resolve(2);
})
    .then(res => { console.log(res) });
    
promise.then(() => {
    return 2;
})
    .then(res => { console.log(res) });

promose.then(2).then(res => {
    console.log(res);
})

第一个:输出 2,需要等5次事件循环,才会走到then,然后输出结果
第二个:输出 2,需要3次循环
第三个:输出 1,then接受的参数是函数,无参数是,会将前面的结果透传

执行队列3

let a;
const b = new Promise((resolve, reject) => {
    console.log('promise1');
    resolve();
}).then(() => {
    console.log('promise2');
}).then(() => {
    console.log('promise3');
}).then(() => {
    console.log('promise4');
});

a = new Promise(async(resolve, reject) => {
    console.log(a)
    await b;
    console.log(a);
    console.log('after1');
    await a;
    resolve();
    console.log('after2');
});
console.log('end');

输出:
promise1
undefined
end
promise2
promise3
promise4
Promise(pending)
after1
解析:代码从往下执行,Promise的回调函数同步执行,所以 首先 输出【promise1】,然后then放入下一个事件队列。接下执行第二个Promise,输出a的定义,此时a 还没有被赋值,所以输出【undefined】,然后等待b返回结果(异步),继续执行当前的事件队列,输出【end】;执行第二个事件队列,加上a中await b,所以接下来会输出【promise2 promise3 promise4】,然后执行await b 后面的内容,输出【after1】
after2没有输出的原因是在前面的a的已经是Promise对象,且状态为pending,指向到await a的时候,需要等a有返回值后才会执行后续的,所以await a后续代码一直没有执行。

Promise的状态只能修改一次

new Promise((resolve, reject) => {
    resolve('success');
    reject('fail');
    resolve('error');
}).then(res => {
    console.log(res);
}).catch(res => {
    console.log(res);
});

输出:success
解析:promise的状态只能修改一次,不可多次修改

限制并发

const uris = [
    'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg',  
    'https://www.kkkk1000.com/images/getImgData/gray.gif', 
    'https://www.kkkk1000.com/images/getImgData/Particle.gif',
    'https://www.kkkk1000.com/images/getImgData/arithmetic.png', 
    'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif', 
    'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg', 
    'https://www.kkkk1000.com/images/getImgData/arithmetic.gif', 
    'https://www.kkkk1000.com/images/wxQrCode2.png',
];
const loadImg = (src) => {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => {
            resolve();
        };
        img.onError = reject;
        img.src = src;
    });
}
function limitLoad(urls, limit) { };
limitLoad(uris, 2);

答案:

function limitLoad(uris, limit) {
    let index = 0;
    const len = uris.length;
    const execPromised = () => {
        if (index < len) {
            console.log(index);
            loadImg(uris[index]).then(() => {
                // setTimeout(() => {
                    execPromised()
                // }, 2000);
            });
            index++;
        }
    };
    for(let i = 0; i < limit; i++) {
        execPromised();
    }
}

解析:setTimeout只是用于观察 我们写的 并发是否正确

依次输出结果

function promise (time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(time);
        }, time);
    });
}
order([
    promise(1000),
    promise(5000),
    promise(3000),
])

答案1:

function order(promises) {
    const len = promises.length;
    const res = [];
    let promise = promises[0];
    for(let i = 1; i <= len; i++) {
        promise = promise.then(data => {
            if (data) {
                res.push(data);
            }
            return promises[i];
        })
    }
    return res;
}

根据promiseA+实现一个自己的Promise

步骤一:实现成功和失败的回调方法
要实现promise最羁绊的功能,首先,需要创建一个构造函数promise,创建一个promise类,在使用的时候传入一个执行器executorexecutor会传入两个参数:成功(resolve)和失败(reject)。前面说过状态只会改变一次,只可能是成功或者失败,所以,默认情况下,在调用成功是,就反悔成功态,调用失败是,反悔失败态。代码如下:

class Promise {
    constructor(executor) {
        this.status = 'pending';
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallBacks = [];
        let resolve = (data) => {
            if(this.status !== 'pending') {
                return;
            }
            this.status = 'resolved';
            this.value = data;
            this.onResolveCallbacks.forEach(fn => fn());
        }
        let reject = (reason) => {
            if(this.status !== 'pending') {
                return;
            }
            this.status = 'rejected';
            this.reason = reason;
            this.onRejectedCallbacks.forEach(fn => fn());
        }
        try{
            executor(resolve, reject);
        } cathc(e) {
            reject(e);
        }
    }
}

步骤二:then方法链式调用
then方法是promise的最基本的方法,入参是两个回调,一个是成功的回调,一个是失败的回调,实现过程如下。

then(onFulfilled, onRejected) {
    if(this.status === 'resolved') {
        onFulfilled(this.value);
    }
    if(this.status === 'rejected') {
        onRejected(this.reason);
    }
}
let p = new Promise(function(resolve) {
    resolve('我是成功');
});
p.then((data) => { console.log(data); }, err => {});
p.then((data) => { console.log(data); }, err => {});

为了实现这样的效果,则then的代码将要重新写,我们可以把每次的调用resolve的结果存入到一个数组中,每次调用的reject结果存入一个数组。这就是为何会在上面两个数组,切分别在resolve 和 reject便利两个数组的额原因,因此,在调用resolve或者reject之前,我们的pending状态是,会把多次then中的结果存入数组中,则上面的代码变为,

then(onFulFilled, onRjected) {
    if(this.status === 'resolved') {
        onFulFilled(this.value);
    }
    if(this.status === 'rejected') {
        onRjected(this.reason);
    }
    if(this.status === 'pending') {
        this.onResolveCallbacks.push(() => {
            onFulFilled(this.value);
        });
        this.onRejectedCallbacks.push(() => {
            onRejected(this.reason);
        })
    }
}

在promise中,要实现链式调用反悔的结果是返回一个新的promise,第一次then中返回的结果,无论是成功或失败,都激昂反悔倒下一次then中的成功态,但是第一次then中如果抛出一次昂错误,则将反悔到下一次then中的失败态中

链式调用成功时
链式调用成功会返回值,有多种情况。因此将链式调用返回的值单独写一个方法。方法中传入四个参数,分别是p2,x,resolve,reject。

  • p2 上一次返回的promise对象
  • x表示运行promise返回的结果
  • resolve是p2的方法
  • reject是p2的方法
function resolvePromise(p2, x, resolve, reject) {
    ……
}
  1. 返回结果不能是自己,返回结果是自己时,永远也不会成功活失败,因此当反悔自己是,抛出一个错误。
function resolvePromise(p2, x, reslove, reject) {
    if(p2 === x) {
        return reject(new TypeError('不能引用自己'))
    }
}
  1. 返回结果可能是promise
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        return reject(new TypeError('不能引用自己'));
    }
    else if(x !== null && (typeof x === 'function' || typeof x === 'object')) {
        let called; // 防止成功后调用失败
        try {
            let then = x.then; // 获取x的then方法
            if(typeof then === 'function') {
                // 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调 
                then.call(x, y => {
                    // 成功和失败只能调用一个
                    if (called) return;
                    called = true;
                    // resolve的结果依旧是promise 那就继续解析
                    resolvePromise(promise2, y, resolve, reject);
               },
               err => {
                   // 成功和失败只能调用一个
                   if (called) return;
                   called = true;
                   reject(err);// 失败了就失败了
               })
            }
        }
        catch(e) {
            if(called) return;
            called = true;
            reject(e);
        }
    }
    else {
        resolve(x);
    }
}