原来你是这样的Promise

1. Promise简介

promise是异步编程的一种解决方案,它出现的初衷是为了解决回调地狱的问题。html

打个比方,我须要:--(延迟1s)--> 输出1 --(延迟2s)--> 输出2 --(延迟3s)--> 输出3,一般写法:java

setTimeout(()=> {
    console.log('1');
    setTimeout(()=> {
        console.log('2');
        setTimeout(()=> {
            console.log('3'); 
        }, 3000)
    }, 2000)
}, 1000)
复制代码

这样的多重的嵌套的回调被称为回调地狱,这样的代码可读性不好,不利于理解。es6

若是用promise的话画风一转编程

function delay(time, num) {
    return new Promise((res, rej)=> {
        setTimeout(()=> {
            console.log(num);
            res();
        }, time*1000)
    });
}
delay(1, 1).then(()=> {
    return delay(2, 2);
}).then(()=> {
    delay(3, 3);
})
复制代码

使用了promise的链式调用,代码结构更清晰。segmentfault

是否是很棒?那还不赶快get起来~数组

2. Promise的使用

调用方式以下:promise

new Promise((resolve, reject)=> {
    if('some option') {
        resolve('some value');
    } else {
        reject('some error');
    }
}).then(
    val=> {
        // ...
    },
    error=> {
        // ...
    }
)
复制代码

Promise构造函数接收一个函数型参数fn,fn有两个参数,分别是:resolve、reject,Promise还有一个Promise.prototype.then方法,该方法接收两个参数,分别是成功的回调函数succ和失败的回调函数error。bash

在fn中调用resolve会触发then中的succ回调,调用reject会触发error回调。app

2.1 参数传递

  • 在fn内部调用resolve/reject传入的参数会做为相应参数传入相应的回调函数
new Promise((res, rej)=> {
    res('happy')
}).then(val=> {
    console.log(val);  // happy
});

new Promise((res, rej)=> {
    rej('error!');
}).then(val=> {}, err=> {
    console.log(err);  // error!
});
复制代码
  • 链式调用时若上一级没有传递值则默认为undefined
new Promise((res, rej)=> {
    res('a');    
}).then(val=> {
    return 'b'
}).then(val=> {
    console.log(val);  // 'b'
}).then((val)=> {
    console.log(val);  // 'undefined'
});
复制代码
  • 若上一级的then中传递的并不是函数,则忽略该级
new Promise((res, rej)=> {
    res('a');    
}).then(val=> {
    return 'b';
}).then(val=> {
    console.log(val);  // 'b'
	return 'c';
}).then({  // 并不是函数
    name: 'lan'
}).then((val)=> {
    console.log(val);   // 'c'
});
复制代码

2.2 参数传递例题

let doSomething = function() {
    return new Promise((resolve, reject) => {
        resolve('返回值');
    });
};

let doSomethingElse = function() {
    return '新的值';
}

doSomething().then(function () {
    return doSomethingElse();
}).then(resp => {
    console.warn(resp);
    console.warn('1 =========<');
});

doSomething().then(function () {
    doSomethingElse();
}).then(resp => {
    console.warn(resp);
    console.warn('2 =========<');
});

doSomething().then(doSomethingElse()).then(resp => {
    console.warn(resp);
    console.warn('3 =========<');
});

doSomething().then(doSomethingElse).then(resp => {
    console.warn(resp);
    console.warn('4 =========<');
});
复制代码

结合上面的讲解想想会输出什么?(答案及解析异步

3. Promise.prototype.then

当Promise中的状态(pending ---> resolved or rejected)发生变化时才会执行then方法。

  • 调用then返回的依旧是一个Promise实例 ( 因此才能够链式调用... )
new Promise((res, rej)=> {
    res('a');
}).then(val=> {
    return 'b';
});

// 等同于
new Promise((res, rej)=> {
    res('a');
}).then(val=> {
    return new Promise((res, rej)=> {
        res('b');
    });
});
复制代码
  • then中的回调总会异步执行
new Promise((res, rej)=> {
    console.log('a');
    res('');
}).then(()=> {
    console.log('b');
});
console.log('c');
// a c b
复制代码
  • 若是你不在Promise的参数函数中调用resolve或者reject那么then方法永远不会被触发
new Promise((res, rej)=> {
    console.log('a'); 
}).then(()=> {
    console.log('b');
});
console.log('c'); 
// a c
复制代码

4. Promise的静态方法

Promise还有四个静态方法,分别是resolverejectallrace,下面咱们一一介绍一下。

4.1 Promise.resolve()

除了经过new Promise()的方式,咱们还有两种建立Promise对象的方法,Promise.resolve()至关于建立了一个当即resolve的对象。以下两段代码做用相同:

Promise.resolve('a');

new Promise((res, rej)=> {
    res('a');
});
复制代码

固然根据传入的参数不一样,Promise.resolve()也会作出不一样的操做。

  • 参数是一个 Promise 实例

若是参数是 Promise 实例,那么Promise.resolve将不作任何修改、原封不动地返回这个实例。

  • 参数是一个thenable对象

thenable对象指的是具备then方法的对象,好比下面这个对象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};
复制代码

Promise.resolve方法会将这个对象转为 Promise对象,而后就当即执行thenable对象的then方法。

  • 参数不是具备then方法的对象,或根本就不是对象

若是参数是一个原始值,或者是一个不具备then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。

  • 不带有任何参数

Promise.resolve方法容许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

值得注意的一点是该静态方法是在本次事件轮询结束前调用,而不是在下一次事件轮询开始时调用。关于事件轮询能够看这里——>JavaScript 运行机制详解:再谈Event Loop

4.2 Promise.reject()

和Promise.resolve()相似,只不过一个是触发成功的回调,一个是触发失败的回调

4.3 Promise.all()

Promise的all方法提供了并行执行异步操做的能力,而且在全部异步操做执行完后才执行回调。

function asyncFun1() {
	return new Promise((res, rej)=> {
		setTimeout(()=> { 
			res('a');
		}, 1000);
	}); 
}
function asyncFun2() {
	return new Promise((res, rej)=> {
		setTimeout(()=> { 
			res('b');
		}, 1000);
	}); 
}
function asyncFun3() {
	return new Promise((res, rej)=> {
		setTimeout(()=> { 
			res('c');
		}, 1000);
	}); 
}
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);
});
Promise.all([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);  // ['a', 'b', 'c']
});
复制代码

用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操做的并行执行的,等到它们都执行完后才会进到then里面。有了all,你就能够并行执行多个异步操做,而且在一个回调中处理全部的返回数据。

适用场景:打开网页时,预先加载须要用到的各类资源如图片、flash以及各类静态文件。全部的都加载完后,咱们再进行页面的初始化。

4.4 Promise.race()

race()和all相反,all()是数组中全部Promise都执行完毕就执行then,而race()是一旦有一个Promise执行完毕就会执行then(),用上面的三个Promise返回值函数举例

Promise.race([asyncFun1(), asyncFun2(), asyncFun3()]).then((val)=> {
    console.log(val);  // a
});
复制代码

5. 链式调用经典例题

看了这么多关于Promise的知识,咱们来作一道题巩固一下。

写一个类Man实现如下链式调用

调用方式:
new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
打印:
'hello, lan' -(等待3s)--> 'lan eat apple' -(等待5s)--> 'lan eat banana'
复制代码

思路:

  • 在原型方法中返回this达到链式调用的目的
  • 等待3s执行的效果能够用Promise & then实现

具体实现以下:

class Man {
    constructor(name) {
        this.name = name;
        this.sayName();
        this.rope = Promise.resolve();  // 定义全局Promise做链式调用
    }
    sayName() {
        console.log(`hello, ${this.name}`);
    }
    sleep(time) {
        this.rope = this.rope.then(()=> {
            return new Promise((res, rej)=> {
                setTimeout(()=> {
                    res();
                }, time*1000);
            });
        });
        return this;
    }
    eat(food) {
        this.rope = this.rope.then(()=> {
            console.log(`${this.name} eat ${food}`); 
        });

        return this;
    }
}

new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
复制代码

ok!不知道你有没有看懂呢?若是能彻底理解代码那你的Promise能够通关了,顺便来个小思考,下面这种写法能够吗?和上面相比有什么区别?:

class Man1345 {
    constructor(name) {
        this.name = name;
        this.sayName(); 
    }
    sayName() {
        console.log(`hello, ${this.name}`);
    }
    sleep(time) { 
        this.rope = new Promise((res, rej)=> {
                setTimeout(()=> {
                    res();
                }, time*1000);
            }); 
        return this;
    }
    eat(food) {
        this.rope = this.rope.then(()=> { 
            console.log(`${this.name} eat ${food}`);  
        });

        return this;
    }
}

new Man('lan').sleep(3).eat('apple').sleep(5).eat('banana');
复制代码

简单的说,第二段代码的执行结果是

'hello, lan' -(等待3s)--> 'lan eat apple' ---> 'lan eat banana'
复制代码

为何会出现这种差异? 由于第二段代码每一次调用sleep都会new一个新的Promise对象,调用了两次sleep就new了两个Promise对象。这两个对象是异步并行执行,会形成两句eat同时显示。

和如下状况相似

var time1 = setTimeout(()=> {
	console.log('a');
}, 1000)
var time2 = setTimeout(()=> {
	console.log('b');
}, 1000)
// 同时输出 a b
复制代码

抽象一点的讲解是:

// 第一段正确的代码的执行为
var p1 = new Promise().then('停顿3s').then('打印食物').then('停顿5s').then('打印食物');

// 第二段代码的执行行为,p一、p2异步并行执行
var p1 = new Promise().then('停顿3s').then('打印食物');
var p2 = new Promise().then('停顿5s').then('打印食物');
复制代码

总结

Promise的常常用到的地方:

  • 摆脱回调地狱
  • 多个异步任务同步

Promise是咱们的好帮手,不过还有另外一种方法也能够作到,那就是async&await,能够多多了解一下。

参考资料

ECMAScript 6 入门

通俗浅显的理解Promise中的then

大白话讲解promise

相关文章
相关标签/搜索