异步解决方法Promise的用法

932 阅读6分钟

异步解决方案——Promise

前言

异步编程模块在前端开发中,显得越来越重要。从最开始的XHR到封装后的Ajax都在试图解决异步编程过程中的问题。随着ES6新标准的到来,处理异步数据流又有了新的解决方案。在传统的ajax请求中,当异步请求之间的数据存在依赖关系的时候,就可能产生不优雅的多层回调,俗称”回调地域“(callback hell),这却让人望而生畏,Promise的出现让我们告别回调地域,写出更优雅的异步代码。

回调地狱带来的负面作用有以下几点:

  • 代码臃肿。
  • 可读性差。
  • 耦合度过高,可维护性差。
  • 代码复用性差。
  • 容易滋生 bug。
  • 只能在回调里处理异常。

在实践过程中,却发现Promise并不完美,Async/Await是近年来JavaScript添加的最革命性的的特性之一,Async/Await提供了一种使得异步代码看起来像同步代码的替代方法。接下来我们介绍这两种处理异步编程的方案。

什么是Promise

Promise 是异步编程的一种解决方案:

从语法上讲,Promise是一个对象,通过它可以获取异步操作的消息;

从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。

promise有三种状态pending(等待态),fulfilled(成功态),rejected(失败态)

状态一旦改变,就不会再变。

创造promise实例后,它会立即执行。

看段习以为常的代码:

// Promise是一个构造函数,自己身上有all,reject,resolve,race方法,原型上有then、catch等方法
let p = new Promise((resolve,reject)=>{
	// 做一些异步操作
	setTimeout(()=>{
	/* 	let res = {
			ok:1,
			data:{
				name:"张三"
			}
		} */
		let res = {
			ok:0,
			error:new Error('有错')
		}
		if(res.ok === 1){
			resolve(res.data);
		}else{
			reject(res.error.message)
		}

	}, 1000)
})

Promise的状态和值

Promise对象存在以下三种状态

  • Pending(进行中)
  • Fulfilled(已成功)
  • Rejected(已失败)

状态只能由 Pending 变为 Fulfilled 或由 Pending 变为 Rejected ,且状态改变之后不会在发生变化,会一直保持这个状态。

Promise的值是指状态改变时传递给回调函数的值

上面例子中的参数为resolve和reject,他们都是函数,用他们可以改变Promise的状态和传入的Promise的值

resolvereject

  • resolve : 将Promise对象的状态从 Pending(进行中) 变为 Fulfilled(已成功)
  • reject : 将Promise对象的状态从 Pending(进行中) 变为 Rejected(已失败)
  • resolvereject 都可以传入任意类型的值作为实参,表示 Promise 对象成功(Fulfilled)和失败(Rejected)的值

then方法

p.then((data)=>{
	console.log(data);
    return data;
},(error)=>{
	console.log(error)
}).then(data=>{
    console.log(data);
})

promise的then方法返回一个promise对象,所以可以继续链式调用

上述代码我们可以继续改造,因为上述代码不能传参

function timeout(ms) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('hello world')
        }, ms);
    })
}
timeout(1000).then((value) => {
    console.log(value);
})

then方法的规则

  • then方法下一次的输入需要上一次的输出
  • 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次then
  • 如果then中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果
  • 如果当前then中失败了 会走下一个then的失败
  • 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功
  • catch是错误没有处理的情况下才会走
  • then中不写方法则值会穿透,传入下一个then

Promise封装XHR对象

const getJSON = function (url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onreadystatechange = handler;
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.send();
        function handler() {
            console.log(this.readyState);
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response);
            } else {
                reject(new Error(this.statusText));
            }
        }
    })
}
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
    .then((res) => {
    console.log(res);

}, function (error) {
    console.error(error);

})

//then方法的链式调用
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
    .then((res)=>{
    return res.HeWeather6;
}).then((HeWeather6)=>{
    console.log(HeWeather6);
})

catch方法

catch(err=>{})方法等价于then(null,err=>{})

getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
    .then((json) => {
    console.log(json);
}).then(null,err=>{
    console.log(err);   
})
//等价于
getJSON('https://free-api.heweather.net/s6/weather/now?location=beijing&key=4693ff5ea653469f8bb0c29638035976')
    .then((json) => {
    console.log(json);
}).catch(err=>{
    console.log(err);   
})

resove()

resolve()方法将现有对象转换成Promise对象,该实例的状态为fulfilled

let p = Promise.resolve('foo');
//等价于 new Promise(resolve=>resolve('foo'));
p.then((val)=>{
    console.log(val);
})

reject()

reject()方法返回一个新的Promise实例,该实例的状态为rejected

let p2 = Promise.reject(new Error('出错了'));
//等价于 let p2 = new Promise((resolve,reject)=>reject(new Error('出错了)));
p2.catch(err => {
    console.log(err);
})

all()方法

all()方法提供了并行执行异步操作的能力,并且再所有异步操作执行完后才执行回调

试想一个页面聊天系统,我们需要从两个不同的URL分别获得用户的的个人信息和好友列表,这两个任务是可以并行执行的,用Promise.all实现如下

let meInfoPro = new Promise( (resolve, reject)=> {
    setTimeout(resolve, 500, 'P1');
});
let youInfoPro = new Promise( (resolve, reject)=> {
    setTimeout(resolve, 600, 'P2');
});
// 同时执行p1和p2,并在它们都完成后执行then:
Promise.all([meInfoPro, youInfoPro]).then( (results)=> {
    console.log(results); // 获得一个Array: ['P1', 'P2']
});

race()方法

有些时候,多个异步任务是为了容错。比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。这种情况下,用Promise.race()实现:

let meInfoPro1 = new Promise( (resolve, reject)=> {
    setTimeout(resolve, 500, 'P1');
});
let meInfoPro2 = new Promise( (resolve, reject)=> {
    setTimeout(resolve, 600, 'P2');
});
Promise.race([meInfoPro1, meInfoPro2]).then((result)=> {
    console.log(result); // P1
});

Promise.all接受一个promise对象的数组,待全部完成之后,统一执行success;

Promise.race接受一个包含多个promise对象的数组,只要有一个完成,就执行success

举个更具体的例子,加深对race()方法的理解

当我们请求某个图片资源,会导致时间过长,给用户反馈

用race给某个异步请求设置超时时间,并且在超时后执行相应的操作

function requestImg(imgSrc) {
   return new Promise((resolve, reject) => {
        var img = new Image();
        img.onload = function () {
            resolve(img);
        }
        img.src = imgSrc;
    });
}
//延时函数,用于给请求计时
function timeout() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('图片请求超时');
        }, 5000);
    });
}
Promise.race([requestImg('images/2.png'), timeout()]).then((data) => {
    console.log(data);
}).catch((err) => {
    console.log(err);
}); 

除了以上Promise的Api相关用法,其实大家更加需要理解的是Promise的A+规范是如何实现的,这也是面试中经常被问到的,希望老铁们加油