解决回调地狱——Promise的使用

Promise 的作用:解决回调问题,为异步操作提供统一的接口,还可以链式调用。

原理:先定义好需要回调的多个函数,然后在每个函数内部“发射(传递)”出下一个函数需要的数据,然后通过链式调用来执行。如果传递的是reject值则停止。

注意:Promise 新建后立即执行,然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行。

let promise = new Promise(function(resolve, reject) {
console.log('Promise');
resolve();
});
promise.then(function() {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved

Promise有三种状态:pending、resolved(fulfilled)、rejected

Promise是一个构造函数,有以下API:

function Promise(resolver) {}
Promise.prototype.then = function(resolve,reject) {
//then的作用是为 Promise 实例添加状态改变时的回调函数
//then方法返回的是一个新的Promise实例,所以可以连写
}
Promise.prototype.catch = function(rejected) {
//它是.then(null, rejection)的别名,用于指定发生错误时的回调函数。
//可以用它捕获错误,错误也会传递,具有传递性
//最好采用这种catch的方式,而不要用then的第二种参数来捕获错误
//如果没有catch,则错误不会被传递,即没有任何报错反应
}
Promise.resolve = function() {
//将现有对象转为 Promise 对象
}
Promise.reject = function() {
//将现有对象转为 Promise 对象
}
Promise.all = function() {
//用于将多个 Promise 实例,包装成一个新的 Promise 实例
//只有所有的参数都变成resolve,最后的状态才返回resolve
//只要多个参数中的任一个reject,则最后的状态返回reject
}
Promise.race = function() {
//用于将多个 Promise 实例,包装成一个新的 Promise 实例
//只要任一个参数返回resolve或者reject,最后的状态就是对应的resolve或者reject
}

最佳实践:

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

简单示例:

var promise = new Promise(function(resolve, reject) {//resolve和reject都为一个函数,他们的作用分别是将状态修改为resolved和rejected
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
promise.then(function(value) {//then有两个参数,分别是执行resolve和reject返回结果的函数
// 如果调用了resolve方法,执行此函数
}, function(value) {
// 如果调用了reject方法,执行此函数
});
//then方法的执行结果也可以返回一个Promise对象,这样就可以进行then的链式执行
promise.then(function(value) {
// 如果调用了resolve方法,执行此函数
}).then(function(value){
//继续执行下一个
});

实际例子:假设下边一个场景,我们一个服务,从一个外边service获取数据,然后写到一个db里,或者一个存储里,最后在把存储的状态龙出来,那么如果没有promise是怎么写的呢?可能会是这样:

//普通回调:
getData(function (value1) {
storeToDb(value1, function(value2) {
logStore(value2, function(value3) {
//...
});
});
});

Promise实现:

function getData(){
return new Promise((resolve,reject) =>{
// ... send request to get data
if(/* get successfully*/){
resolve(data)
}else{
reject(err)
}
})
}
function storeData(data){
return new Promise((resolve,reject)=>{
// ... store the data
if(/*store successfully*/){
resolve(data)
}else{
reject(err)
}
})
}
getData()
.then(data => storeData(data))
.then(data => console.log('the process is done',data));
.catch(err => console.error('there is the err',err));

这样写是不是就是很清楚了,先getData,然后再storeData,最后将这次运行的情况log了出来,其中有任何的问题,在catch中都可以Catch出来。

利用Promise封装一个ajax:

// 封装一个get请求的方法
var url='xxx';
function getJSON(url) {
return new Promise(function(resolve, reject) {
var handler = function() {
if (this.readyState != 4){
return
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
}
var XHR = new XMLHttpRequest();
XHR.open('GET', url, true);
XHR.onreadystatechange = handler;
XHR.responseType = "json";
XHR.setRequestHeader("Accept", "application/json");
XHR.send();
})
}
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});

封装一个加载图片的操作:一旦加载完成,Promise的状态就发生改变

var preloadImage = function (path) {
return new Promise(function (resolve, reject) {
var image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};

Promise.all([fn1,fn2…]):

Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。

var url1 = 'aaa';
var url2 = 'bbb';
function renderAll() {
return Promise.all([getJSON(url1), getJSON(url2)]);//注意Promise.all没有顺序关系
}
renderAll().then(function(value) {
console.log(value);
})

Promise.race([fn1,fn2…]):

Promise.race都是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promsie状态变成resolved或者rejected时,就可以调用.then方法了。

function renderRace() {
return Promise.race([getJSON(url1), getJSON(url2)]);
}
renderRace().then(function(value) {
console.log(value);
})

有用的附加方法

done():用于捕获错误到全局

asyncFunc()
.then(f1)
.catch(r1)
.then(f2)
.done();
//实现代码:
Promise.prototype.done = function (onFulfilled, onRejected) {
this.then(onFulfilled, onRejected)
.catch(function (reason) {
// 抛出一个全局错误
setTimeout(() => { throw reason }, 0);
});
};

从上面代码可见,done方法的使用,可以像then方法那样用,提供fulfilled和rejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。

finally():用于不管前面的结果如何,接下来都要执行的操作

server.listen(0)
.then(function () {
// run test
})
.finally(server.stop);//不管前面怎样,都要关掉服务器
//实现代码
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};

上面代码中,不管前面的 Promise 是fulfilled还是rejected,都会执行回调函数callback。


async

async 是ES7标准提出的函数,async 函数返回值是 Promise 对象,可以直接使用 then() 方法进行调用。

async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)。

await 会等待 Promise 完成,并返回 Promise 的结果,然后再执行后面的代码。同时只能用在 async 函数中使用。

function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(ms);
}
asyncPrint('hello world', 5000);//5秒后才会打出hello world

正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise。

使用:

function getData(){
console.log("这里是返回的数据")
}
async function test(){
return getData()
}

async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return 'done';
}
f().then(v => console.log(v)); // 等待6s后才输出 'done'

参考:http://es6.ruanyifeng.com/#docs/promise