Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promise 总结 #38

Open
SunShinewyf opened this issue Jan 23, 2018 · 0 comments
Open

Promise 总结 #38

SunShinewyf opened this issue Jan 23, 2018 · 0 comments

Comments

@SunShinewyf
Copy link
Owner

SunShinewyf commented Jan 23, 2018

最近接触到关于异步处理的一些业务,发现对异步的解决方案的用法还不是很熟悉,借此机会巩固一下关于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用法不太熟悉的基础上,整理出以上内容,有觉得不太正确的地方的可以一起交流~

上文中的示例代码地址

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant