promise原理以及async-await语法

251 阅读6分钟
  • [前言]

  • [一、回调地狱是什么?]

  • [二、如何解决回调地狱]

    • [1.Promise]
    • [2.async/await]
  • [总结] 一. 回调地狱是什么

  • js中或者node,都大量的使用了回调函数进行异步操作,而异步操作什么是返回结果是不可控的

  • 如果我们希望几个异步请求按照顺序来执行,那么就需要将这些异步操作嵌套起来

  • 嵌套的层数特别多,就会形成横向金字塔,也叫做回调地狱

说人话:代码中回调函数嵌套回调函数,这种回调函数中嵌套回调函数的情况就回调地狱

image.png

小结一下: 回调地狱就是为了实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差.后期不好维护

二. 如何解决回调地狱

promise是js中一个原生对象,它是一个ES6提出一个新语法,用来优化异步代码的写法,可以替换掉传统的回调函数解决方案

promise使用如下

var p1 = new Promise(function(resolve,reject){
     //异步操作 resolve(obj1)  或者 reject(obj2)
});
p1.then(function(rs){
    // 如果p1的状态是resolved,则then中的函数
    //会执行,且obj1的值会传给rs
}).catch(function(rs){
    // 如果p1的状态是reject,则catch中的函数
    //    会执行,且obj2的值会传给rs
}).finally(function(){
    // 一定会执行的函数
})

image.png

then方法的链式调用

  • 如果前一个then返回一个普通值,则后一个then可以得到这个值
  • 如果前一个then返回一个 Promise对象,则后一个then可以得到 Promise对象成功状态的结果

image.png

构造器

// const obj = new Object()
// const arr = new Array()
const p1 = new Promise(function(resolve,reject){
  // 执行异步代码
  // 调用resolve,或者reject
});
console.dir(p1)

要点:

  • 构造器必须要给定一个参数,如果不给就是会报错。例如,new Promise() 报错的信息是: Promise resolver undefined is not a function
  • 构造器的实参是一个函数,这个函数的特殊之处在于它有两个形参(resolve,reject),这两个形参也是函数。在格式上,也可以采用箭头函数来改写。例如:var p1 = new Promise((resolve,reject)=>{})
  • 在函数体的内部, 一般会执行异步代码,然后根据情况来调用resolve()或者是reject() 。调用resolve或者是reject后会产生什么样的后果,在后面小节介绍。 当然了,再次强调一下resolve和reject只是形参名,可以改写成其它的。

promise的三种状态

image.png

image.png

then的返回值(难点)

then()方法的返回值也是一个promise对象,所以它支持链式写法。但是要注意的是它的返回值是一个新的promise对象,与调用then方法的并不是同一个对象。

看下如下代码:

var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){}); 
// p2也是一个promise对象。

console.log(p1 === p2); // false

如上代码可以说明p1.then()的结果是一个与p1不同的promise对象。换句话说,then()会封装一个全新的promise对象p2。那既然 p2也是一个promise对象,那么,p2的状态(promiseStatus)和值(promiseValue)分别是什么?

p2的状态及promiseValue如何确定?

p2的状态及promiseValue按如下规则来确定

  • 如果p1的状态是pending,则p2的状态也是pending。
  • 如果p1的状态是resolved,then()会去执行f_ok,则p2的状态由f_ok的返回值决定。
    • 如果f_ok返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_ok函数的return值。
    • 如果f_ok返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
    • 如果f_ok这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。
  • 如果p1的状态是rejected,then()会去执行f_err,则p2的状态由f_err的返回值决定。
    • 如果f_err返回值不是promise对象,则p2的状态是resolved,且p2的promiseValue就是f_err函数的return值。
    • 如果f_err返回值是一个promise对象,则p2的状态及promiseValue以这个promise对象为准。
    • 如果f_err这个函数内部发生了错误(或者是用户主动抛出错误),则p2的状态是rejected,且p2的promiseValue就是这个错误对象。

示例代码1

var p1 = new Promise(()=>{});
var p2 = p1.then(function f_ok(){}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1); // pending
console.dir(p2); // pending

示例代码2

var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
	return 1
}, function f_err(){}); // p2也是一个promise对象。
console.dir(p1);	// resolved, undefined
console.dir(p2); // resolved, 1

特殊地,如果f_ok()中并没有return语句,则相当于是 return undefined。

示例代码3

var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
	var temp = new Promise((resolve,reject)=>{ resolve({a:1}) }); 
	return temp;
}, function f_err(){}); 

console.dir(p2); // resolved, {a:1}

示例代码4

var p1 = new Promise((resolve,reject)=>{ resolve()});
var p2 = p1.then(function f_ok(){
	console.log(abc);// 这里故意犯错
}, function f_err(){}); 

console.dir(p2);

示例代码5

var p1 = new Promise((resolve,reject)=>{ reject(100)});
var p2 = p1.then(function f_ok(){
	
}, function f_err(errVal){
	var temp = new Promise((resolve,reject)=>{ resolve({b:errVal}) }); 
	return temp;
}); 

console.dir(p2);

示例代码6

var p1 = new Promise((resolve,reject)=>{ reject(100)});
var p2 = p1.then(function f_ok(){
	
}, function f_err(errVal){
	throw new Error("aaa")
}); 
console.dir(p2)

async-await语法

  • async和await是 ES2017 中提出来的
  • async和await两个关键字的出现,简化的Promise的使用
  • async用于修饰一个function
  • async修饰的函数,总是返回一个Promise对象
  • 函数的返回值,将自动包装在resolve的 promise中
  • await只能出现在async函数内
  • await让JS引擎等待直到promise完成并返回结果
  • ·语法: let value = await promise对象;//等待promise对象的结果,然后将结果赋值给value
  • 由于await需要等待promise执行完毕,所以await会暂停函数的执行,但不会影响其他同步任务
  • // 1.await只能出现在async修饰的函数中!
  • // 2.await后面跟随的是一个promise对象;
  • // 3.await能停止代码执行,让后面的同步代码,先执行;
  • // 4.await返回的是: Promise对象中的then)中的回调函数中的参数res;

示例

        //封装一个返回promise的异步任务
        function fn(str) {
            var p = new Promise(function (resolve, reject) {
                var flag = true;
                setTimeout(function () {
                    if (flag) {
                        resolve(str)
                    } else {
                        reject('处理失败')
                    }
                })
            })
            return p;
        }

        //封装一个执行上述异步任务的async函数
        async function test(){
            var res1=await fn('前端要以和为贵');  //await直接拿到fn()返回的promise的数据,并且赋值给res
            var res2=await fn('要讲武德');
            var res3=await fn('不要搞窝里斗');
            console.log(res1,res2,res3);
        }
        //执行函数
        test();        // 前端要以和为贵 要讲武德 不要搞窝里斗

总结一下:

当我们写代码遇到异步回调时,我们想让异步代码按照我们想要的顺序执行,如果按照传统的嵌套方式,就会出现回调地狱,这样的代码不利于维护,我们可以通过Promise对象进行链式编程来解决,这样尽管可以解决问题,但是ES7给我们提供了更加舒适的async/await语法糖,可以使得异步代码看起来更像是同步代码。