SunShinewyf/issue-blog

Promise 总结

SunShinewyf opened this issue · 0 comments

最近接触到关于异步处理的一些业务,发现对异步的解决方案的用法还不是很熟悉,借此机会巩固一下关于Promise的用法,文章中的示例代码地址

对于PromiseA+规范的一些讲解,这里不会涉及,如果对Promise原理性的东西还不太了解的,可以直接移步这里

关于Promise

众所周知,js中最原始的异步解决方案就是回调函数,但是回调函数引发的问题就是臭名昭著的callback hells,关于异步的演化可以移步我之前比较早的这篇介绍,这里不再赘述。

Promise是一种异步解决方案,它有resolvereject两个回调参数,resolvePromisepending状态变为fulfilled状态后触发的回调函数。而reject则是状态从pending状态变为rejected后触发的。 而且一个Promise对象的状态只能从pending->fulfilled或者从pending->reject,不可能同时达到两种状态,例如:

let promise = function () {
    return new Promise((resolve, reject) => {
        resolve(1);
        throw new Error('err');
    })
}

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

//打印 1,而不会打印出 Error:err

上面的例子,之后打印出1而不会捕捉到抛出的异常,也就是在执行resolve(1)的时候,Promise的状态已经从pending状态变为fulfilled的了,所以此时抛出异常也不会再将状态变为rejected

Promisethen()

thenPromise类原型对象的一个方法,它主要是为Promise对象实例添加状态改变时的回调函数,其函数声明为:then(resolve,reject),resolve参数是状态变为fulfilled时的回调函数,reject参数是状态变为rejected时的回调函数,而且第二个参数reject是可选参数。查看例子。当数字大于3的时候就会被进入reject,否则就会进入resolve的回调。

但是当Promise的状态一直是pending的时候,就会无法进入到then的任何一个回调中.例如:

let promise = function () {
    return new Promise((resolve, reject) => {
        console.log('I am pending');
    })
}

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

上面的例子,由于Promise实例没有从pending进入到任何一个结果状态,所以也就不会执行then里面的任何一个回调。
fulfilled的回调中不返回任何值时,在then的第一个回调函数中就会得到一个undefined
例如:

let promise = new Promise((resolve, reject) => {
    resolve('promise')
})


promise.then(function () {
    //dosomething
}).then((res) => {
    console.log(res)
})

//undefined

Promise里面的catch()

catch用于指定发生错误时的回调函数,它是then(null, rejection)的别名,也就是:

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

 //等同于,其中promise是一个Promise实例
 promise.then((res) => { console.log('resolve', res) })
     .then(null, (err) => { console.log('reject', err) });

Promise的错误具有冒泡机制,也就是前面产生的错误,可以一直向后传递,直到被catch捕获为止。要是最外层没有catch用来捕获错误,错误就会冒泡到最外层,然后就会触发unhandledRejection事件。除此之外,catch方法返回的仍旧是一个promise实例,所以后面可以继续接catch()或者then(),详见示例代码

Promise.all()

Promise.all方法接受一个数组作为参数,数组中的值如果不是Promise对象,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例。Promise.all()是将多个Promise对象包装成一个Promise实例。例如:

  let promise1 = new Promise((resolve, reject) => {
    resolve('promise1');
})

let promise2 = new Promise((resolve, reject) => {
    resolve('promise2');
})

Promise.all([promise1, promise2]).then((res) => {
    console.log('promise-all', res)
}).catch((err) => {
    console.log('err', err);
})

//promise-all ['promise1','promise2'];

对于Promise.all()返回的Promise对象的状态,是一种与门的返回结果,也就是只有promise1promise2都是fulfilledPromise.all()才会变成fulfilled状态,只要有一个是rejected,那么它最终的状态也将是rejected状态。

Promise.race()

Promise.all()类似,Promise.race()也是接受一个数组作为参数,并且如果数组中的值类型不是Promise对象,仍然会先调用Promise.resolve()将数组中的值转为一个Promise实例。

Promise.all()不同的是,Promise.race()是看谁执行得快,它的最终状态也是随着最先一个实例的状态的改变而改变:

let promise1 = new Promise((resolve, reject) => {
    resolve('promise1');
})

let promise2 = new Promise((resolve, reject) => {
    resolve('promise2');
})

Promise.race([promise1, promise2]).then((res) => {
    console.log('promise-race', res);
}).catch((err) => {
    console.log('err', err);
})

// promise-race promise1

Promise的值穿透

值穿透的场景有以下两种:

  • Promise实例的状态已经是fulfilled或者rejected的时候,通过then会返回this
    由于then的代码如下:
Promise.prototype.then = function (onFulfilled, onRejected) {
  if (!isFunction(onFulfilled) && this.state === FULFILLED ||
    !isFunction(onRejected) && this.state === REJECTED) {
    return this;
  }
  ...
};

实例讲解:

const func1 = function () {
    return new Promise((resolve, reject) => {
        resolve('promise1');
    })
}

const func2 = function () {
    return new Promise((resolve, reject) => {
        resolve('promise2');
    })
}

func1().then(func2()).then((res) => {
    console.log(res)
})


//promise1

在第一个then中由于传入了一个promise类型(非Function),所以func2()Promise不会传递到后面,而此时promise的状态已经是fulfilled了,从而打印出func1()中的resolve的值,
当改成如下这样就可以了:

const func1 = function () {
    return new Promise((resolve, reject) => {
        resolve('promise1');
    })
}

const func2 = function () {
    return new Promise((resolve, reject) => {
        resolve('promise2');
    })
}

func1().then(func2).then((res) => {
    console.log(res)
})
  • 由父Promise衍生出的子Promise,并且当子Promise实例状态为pending时,子Promise的状态由父Promise设定:
let promise1 = new Promise((resolve, reject) => {
    resolve('promise1');
})

let promise2 = promise1.then();

promise2.then((res) => {
    console.log(res)
})

//promise1

这种情况其实是上面情况的一种变种,不解释。
总结:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

Promise实践

可知长度的关联串行

这种场景类似于:taskB要依靠taskA的数据去请求,taskC又要依靠taskB返回的数据来请求数据,这种是典型的关联串行的模式,但是又是可知长度的,这种可以直接采取下面这种模式:

taskA().then((res1) => {
    return taskB(res1)
}).then((res2) => {
    return taskC(res2)
}).then((res3) => {
    console.log(res3);
})


其中`taskA,taskB,taskC`的结构都是类似如下:

let taskA = new Promise((resolve, reject) => {
    rp(urlA).then((res) => {
        resolve(res)
    })
})

在这种情况下,在then()中都需要返回一个Promise实例,否则会出现掉链

未知长度的关联串行

场景示例:在请求某个接口数据时,需要分页去请求,传递offset值和count值去请求,每次请求返回的结果是数据以及nextOffset,这种情况下,就是判断最终这个nextOffset是否为-1来终止循环次数,从而形成了一种有关联的但是是未知长度的串行请求。

function eachTask(offset) {
    let option = {
        url: 'yourUrl',
        offset: offset,
        count: 30
    }

    return new Promise((resolve, reject) => {
        rp(option).then(function (res) {
            resolve(res);
        })
    })
}

let list = [];
const allTask = (offset = 0) => eachTask(offset).then(res => {
    res && (list = list.concat(res.data));
    if (res.nextOffset && res.nextOffset != -1) {
        return eachTask(res.nextOffset);
    } else {
        return Promise.resolve(list);
    }
})


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

并行请求

使用Promise实现并行请求,需要使用到Promsie.all(),处理方式类似于下面这种:

getPromiseArr()
    .then((arr) => {
        return Promise.all(arr);
    })
    .then((res) => {
        console.log(res);
    }).catch((err) => {
        console.log('err', err);
    })

其中getPromiseArr()可以返回一个Promise类型的数组。

基于对Promise用法不太熟悉的基础上,整理出以上内容,有觉得不太正确的地方的可以一起交流~

上文中的示例代码地址

参考资料