Promise使用

160 阅读4分钟

上周学习了Promise原理,知道原理之后在自己项目中实践了一下,觉得有必要总结一下常用用法。

1、Promise.all(并行)

使用:Promise.all(array)

* Promise.all()执行返回一个promise对象

* 参数array是一个数组,数组中包含了要执行的promise对象,只有数组中的每个promise对象执行成功resolve,才会执行之后的.then操作。只要有一个promise对象执行失败reject,Promise.all()便会中止执行

* 当执行成功时,then中回调函数得到一个数组,结果依次是promise对象resolve返回的结果

下面来看一个例子:

A,B,C依次放入promise.all中,

var A = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("A")
        },1000)
    })
}
var B = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("B")
        },2000)
    })
}
var C = function(){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("C")
        },3000)
    })
}
var startTime = new Date().getTime()
Promise.all([A(),B(),C()]).then(function(results){
    console.log("执行完成时间:" + (new Date().getTime()-startTime)+"ms")
    console.log(results)
})

// 执行完成时间:3002ms
//     [ 'A', 'B', 'C' ]

通过多次执行,得到执行的完成时间平均为3000ms,说明了三个函数A、B、C是并行执行

2、Promise.race(得到一个promise对象返回的数据)

使用:Promise.race(array)

*Promise.race和all方法用法类似,不过只要array中的任一promise对象返回resolve或reject,then或者catch中的回调函数就会执行并且只得到第一个resolve或reject返回的值。(谁先执行就返回谁的数据)

*虽然then或catch的回调函数只得到最先执行的promise对象返回的resolve或reject,但是其他的promise对象依旧会继续执行,只不过我们得不到它们返回的数据。

function delayPromise(ms) {
    return new Promise(function(resolve,reject){
        setTimeout(function(){
            resolve("timeout!")
        },ms)
    })
}

function normalPromise(){
    return new Promise(function(resolve,reject){
        setTimeout(function () {
            resolve("success!")
        },2000)
    })
}

function settle(cb,ms){
    var timeout = delayPromise(ms)
    return Promise.race([cb(),timeout])
}
settle(normalPromise,1200).then(function(result){
    console.log(result)
}).catch(function (e) {
    console.log(e)
})

//timeout!

3、按顺序执行

重复使用多个then的方法中的实现先执行A方法再执行B方法(A、B都是异步执行)

function A (){
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(100)
        },1200)
    })
}

function B(){
    return new Promise(function(resolve){
        setTimeout(function () {
            resolve(120)
        },1000)
    })
}

function main() {
    var array = []
    return A().then(function(value){
        array.push(value)
        return array
    }).then(B).then(function (value) {
        array.push(value)
        return array
    })
}

main().then(function (value) {
    console.log(value)
}).catch(function (e) {
    console.log(e)
})

// [100, 120]

以上执行能满足顺序执行异步方法,但是如果方法变多,在main中的then方法就会变多,这样的代码看起来很冗长。

下面我们结合数组的遍历来改变一下main方法,数组遍历有for循环,还有Array.prototype.reduce,这里我们选择reduce。

function main(){
    var array = []
    var tasks = [A, B]
    return tasks.reduce(function(promise,task){
        return promise.then(task).then(function(value){
            array.push(value)
            return array
        })
    },Promise.resolve())
}

main().then(function (value) {
    console.log(value)
}).catch(function (e) {
    console.log(e)
})

// [100, 120]结果还是一样

4、异常处理

* promise内部错误不影响外部函数执行

function catchError(){
    return new Promise(function(resolve,reject){
       throw new Error("something got error")
    })
}
catchError().then(function (result) {
    console.log(result)
},function (error) {
    console.log(error)
})

console.log("1111")

//1111
//Error: something got error

*then的回调函数里面的错误

function errorCame(){
    return new Promise(function(resolve,reject){
       resolve("ok")
    })
}
errorCame().then(function (result) {
    console.log(result)
    throw new Error("a error came")
},function (error) {
    console.log(error)
})

// ok
/*回调函数中的错误未被捕获*/

*捕获错误

//方式一:
function getError(){
    return new Promise(function(resolve,reject){
        resolve("ok")
    })
}
getError().then(function (result) {
    console.log(result)
    throw new Error("a error came")
},function (error) {
    console.log(error)
}).catch(function (e) {
    console.log(e)
})

//ok
//Error: a error came
/*这里的错误被catch捕获*/
//方式二:
getError().then(function (result) {
    console.log(result)
    throw new Error("a error came")
},function (error) {
    console.log(error)
}).then(undefined,function (error) {
    console.log(error)
})
//ok
//Error: a error came

5、不能断链

*每次调用then会创建一个新的Promise对象

var aPromise = new Promise(function (resolve) {
    resolve(100);
});
var thenPromise = aPromise.then(function (value) {
    console.log(value);
});
var catchPromise = thenPromise.catch(function (error) {
    console.error(error);
});
console.log(aPromise === aPromise); // => true
console.log(aPromise !== thenPromise);// => true
console.log(thenPromise !== catchPromise);// => true

所以要注意以下错误用法:

function mistake() {
    var promise = Promise.resolve(15);
    promise.then(function(v) {
        return v
    });
    return promise;
}

正确用法:

function correct() {
    var promise = Promise.resolve(15);
    return  promise.then(function(v) {
        return v
    });
}

6、一道面试题

var prop = new Promise(function(resolve,reject){

    var prop1 = new Promise(function(resolve,reject){
        setTimeout(function(){
            console.log("setTimeout");
            resolve("setTimeout")
        });
        resolve("resolve promise 2");
        console.log("promise2");
    });

    console.log("promise1");
    resolve("resolve promise 1");

    prop1.then(function(arg){
        console.log(arg)
    });    

});

prop.then(function(arg){
    console.log(arg);
});

console.log("end");

这里的结果是:

promise2 ->  promise1 ->   end  ->  resolve promise 2  ->   resolve promise 1 ->      setTimeout

这里涉及到事件队列(eventloop)

为什么是这个顺序呢?

首先我们来了解一下同步任务和异步任务的执行顺序,这里有两个概念:macrotask 与 microtask

macrotask:setTimeout, setInterval, setImmediate, I/O, UI rendering

microtask:process.nextTick, Promise, MutationObserver

一个事件循环包括一个macrotask和任意个microtask。一个事件循环结束才会进行下一个事件循环,直至执行完毕。

分析以上结果:

1)整个javascript是一个macrotask,所以从上之下执行代码,promise内部是同步执行的。(promise2 -> promise1 -> end) 这是同步执行得到的结果。

2)promise.then的回调函数被推入microtask,按照先进先出原则, resolve promise 2 -> resolve promise 1。 到这里一个事件循环结束。

3)从setTimeout开始,一个新的事件循环开始,得到setTimeout。最后执行结束。

以上大致分析了事件循环,要指出的是Promise的构造函数依旧是同步执行的,其then的回调是microtask异步执行。


参考文章:

https://juejin.cn/post/6844903694069137421

http://liubin.org/promises-book/#promise-done

https://www.mattgreer.org/articles/promises-in-wicked-detail/#defining-the-promise-type

http://www.ruanyifeng.com/blog/2014/10/event-loop.html