回调地狱终结者——Promise

在Web前端开发中,我们使用JavaScript会大量依赖异步计算。比如说,Ajax请求时,我们可能会需要不只一个请求来达到某种目的,此时需要后面的请求依赖于前面请求的结果。这种情况在简单的业务中并无大碍,但当我们遇到多个请求时,为了保证依赖顺序,必须进行嵌套,臃肿的代码就是我们常说的“回调地狱”问题。为了解决问题,ES6根据Promise/A+推出了Promise语法。

文章目录

        • 1. 简单运用Promise
        • 2. Promise三个状态
        • 3. Promise链式调用
        • 4. Promise.all
        • 5. Promise.race
        • 6. Axios——Promise封装的Ajax
        • 7. async/await

1. 简单运用Promise

function sleep(time) {
     
    return new Promise(function(resolve, reject) {
     
    	if(time < 1000) {
     
    		setTimeout(resolve("You are success"), time);
    	}
        else {
     
        	reject("fail");
        }
    })
}

sleep(500).then(msg => {
     
	console.log(msg);
}).catch(err => {
     
	console.log(err);
});

控制台查看结果:
回调地狱终结者——Promise_第1张图片

一般在使用Promise的时候,会在定义的函数中返回一个Promise构造函数,该构造函数包含两个参数,分别是resolve和reject。在上面的代码中,我们调用sleep方法,传入500。而我们使用了Promise的内置方法then,并且向then中传两个参数,一个是成功回调函数,一个是失败回调函数(本例中省略第二个参数)。当承诺成功兑现,第一个回调就会被调用,而当出现错误的时候,就会调用第二个参数reject(此处省略)。由于500小于1000,因此会执行setTimeout(resolve(“You are success”), time),输出了You are success。
在上面的例子中,我们还使用了Promise中的catch方法,当传入的参数大于500,则会执行catch方法,catch方法中传递一个参数,也就是reject,因此在失败的时候会执行它。如下:
回调地狱终结者——Promise_第2张图片

2. Promise三个状态

promise对象用于作为异步任务结果的占位符,代表着一个我们暂时还没获得但未来有希望获得的值。正是因为这个原因,js的工程师们为Promise设置了三种状态,分别是等待态(pending),接受态(fullfilled),失败态(rejected)。当resolve函数被调用,promise的状态就会变成接受态;反之,如果reject方法被调用,或者在promise调用过程中发生了一个未处理的异常,则会进入失败态。一旦状态改变一次,promise将不会再次改变状态。

3. Promise链式调用

在本文开头,我们提到了回调地狱,最为常见的就是使用Ajax时,由于多个相互关联的请求多层嵌套,造成语法臃肿,对于后续的维护产生了不好的影响。而promise的链式调用,一定程度上解决了这个问题。我们再回到代码:

sleep(500).then(msg => {
     
	console.log(msg);
}).catch(err => {
     
	console.log(err);
});

此处使用了then方法,当promise成功兑现,则触发回调函数。我们还会发现,除了打印出You are success之外,还返回了一个Promise对象。
回调地狱终结者——Promise_第3张图片

这就是说,每次then()都会返回一个新的promise,而这一个promise则可以交给下一个then方法使用。如下所示,每次then方法之后都返回了sleep(),而我们给出的sleep方法就是返回一个promise实例化对象,这样不断交给下一个then方法执行,就是我们的链式调用。

sleep(500)
.then(msg => {
     
	console.log(msg);
	return sleep(500);
})
.then(msg => {
     
	console.log(msg);
	return sleep(500);
})
.then(msg => {
     
	console.log(msg);
	return sleep(500);
})
.then(msg => {
     
	console.log(msg);
	return sleep(1500);
})
.catch(err => {
     
	console.log(err);
});

控制台输出:
回调地狱终结者——Promise_第4张图片

4. Promise.all

Promise除了链式调用实现多个步骤相互依赖之外,还提供了同时等待多个异步任务的方法,这里使用Promise中的all。

Promise.all([ sleep(500), sleep(600), sleep(1500)])
	.then(result => {
     
		console.log(result);
	})
	.catch(err => {
     
		console.log(err);
	})

控制台输出如下:
回调地狱终结者——Promise_第5张图片

在这个方法的使用中,我们不关系任务执行的顺序。使用的时候,我们传入一个数组,元素则是promise对象。当调用all的时候,会等待所有的promise对象被兑现之后,返回一个成功值数组。
但是,当其中一个promise失败,则会影响整个结果,如下所示:
回调地狱终结者——Promise_第6张图片

5. Promise.race

当我们只关心第一个返回结果的promise时,Promise.race能帮你达成这个愿望。如同Promise.all一样,我们在使用race的时候会传入一个promise对象数组,,一旦该数组中的一个promise被兑现,则返回结果。为了印证这一点,我们改造一下sleep方法,如下:

function sleep(time) {
     
    return new Promise(function(resolve, reject) {
     
    	if(time < 1000) {
     
    		setTimeout(resolve(`${
       time}s later...`), time);
    	}
        else {
     
        	reject("fail");
        }
    })
}

Promise.race([ sleep(500), sleep(600), sleep(700)])
	.then(result => {
     
		console.log(result);
	})

控制台输出如下:
回调地狱终结者——Promise_第7张图片

6. Axios——Promise封装的Ajax

在前端开发中,异步向后端发起请求有很多中方法,jQuery的Ajax,fetch,以及axios。axios的官网(http://www.axios-js.com/zh-cn/docs/)上是这样介绍的。
回调地狱终结者——Promise_第8张图片
和jQuery的Ajax功能相似,都是对XMLHttpRequest的封装,让我们能够更好地进行异步请求。在Axios官网中还提到:“使用Promise管理异步,告别传统callback方式”,这里说的就是Promise的链式调用。正是这种语法的使用,让回调地狱问题得到了很好的解决。axios本质上也是对原生XHR的封装,只不过它是Promise的实现版本。
Axios简单使用代码如下:

axios.get(url)
  .then(function (response) {
     
    console.log(response);
  })
  .catch(function (error) {
     
    console.log(error);
  });

本质上Axios的实例化对象就是一个promise,因此当我们熟悉了promise之后再来使用axios会比较容易上手。
为了加深理解,这里使用Promise封装一个XMLHttpRequest,模拟axios。代码如下:

function getJSON (url) {
     

    return new Promise( (resolve, reject) => {
     
        var xhr = new XMLHttpRequest()
        xhr.open('GET', url, true)

        xhr.onreadystatechange = () => {
     
            if (this.readyState === 4) {
     
                if (this.status === 200) {
     
                    resolve(this.responseText)
                } else {
     
                    var resJson = {
      
                    	code: this.status, response: this.response 
                    }
                    reject(resJson, this)
                }
            }
        }
        xhr.send()
    })
}

getJSON ("https://blog.csdn.net/")
	.then(res => {
     
  		console.log(res); 
	})
	.catch(error => {
     
    	console.error(error);
	});

非常遗憾,出现了跨域问题,但是没关系,主要是通过一个实际的例子让帮助大家更进一步理解promise。

7. async/await

不知道大家发现没有,Promise虽然一定程度上解决了回调地狱问题,但是链式调用的语法仍然不够优雅,我们如果能用完全同步的语法来解决异步问题,那该有多好。在ES6中,我们可以使用生成器和promise相结合的方式实现这个愿望。而JS伟大的工程师们为我们提出了更好的方案,async/await,这号称是JavaScript解决异步问题的终极解决方案,其实是对Promise的再一次封装,也可以说是语法糖。那么它到底如何,就请听下回分解吧!

你可能感兴趣的:(JavaScript,javascript)