关于Promise对象的使用及原理,以及async、await关键字的用法

255 阅读7分钟

关于Promise对象的使用及原理,以及async、await关键字的用法

一、什么是异步编程

1.1、JavaScript 最开始就被设计成了单线程,这也是这门语言的核心特点之一

​ 如果有多个任务就必须排队,一个个依次执行,这种模式的优点就是更安全、更简单,缺点就是遇到遇到耗时任务后面的任务都需要排队等待,可能导致程序出现假死的情况。

为了解决耗时任务的执行,JavaScript 将任务的执行模式分成了两种,分别是同步模式异步模式

1.2、同步模式

​ 代码中的任务依次执行,后一个必须等待前一个任务结束才能执行,程序的执行顺序和代码的编写顺序是一致的,这种方式会比较简单,JavaScript 在单线程模式下大多数任务都会按照同步模式执行。

1.3、异步模式

​ 异步模式的 api 不会等待当前任务结束才开始执行下一个任务,对于耗时操作都是开启之后就立即往后执行下一个任务,耗时任务的后续逻辑一般通过回调函数的方式定义,等异步任务执行结束后就会调用回调函数。异步模式对 JavaScript 非常重要,如果没有异步模式单线程的 JavaScript 语言就无法同时处理大量耗时任务,对于开发人员来说,异步模式下的代码执行顺序会出现跳跃,不会像同步代码那样通俗易懂。

异步api有 setTimeout setInterval ajax请求 读取文件的操作等等

console.log('头部');
setTimeout(() => {
    console.log('timeout1')
}, 1800);


setTimeout(() => {
    console.log('timeout2')
    setTimeout(() => {
        console.log('inner timeout')
    }, 1000)
}, 1000);
console.log('尾部');
1.4、回调函数

​ 这是 JavaScript 异步编程的根本方式,其实所有异步函数的根本都是回调函数,回调函数可以理解为知道要执行什么事情,但是不知道这个事情依赖的任务什么时候完成,所以说最好的办法就是把这些事情的步骤写到一个函数(回调函数)当中,交给任务的执行者,执行者是知道什么时候结束,等结束之后由异步任务的执行者执行。以 ajax 为例,ajax 就是希望拿到数据之后去做一些处理,但是请求什么时候完成不知道,所以得把请求响应之后要执行的任务放到函数中,ajax 执行完成之后会自动执行这些任务,这种由调用者定义,交给执行者执行的函数就被称之为回调函数。具体方法也很简单,就是把函数作为参数传递,只不过这种方式不利于阅读,而且执行顺序也会非常混乱。

1.5、Promise

​ 回调函数是 JavaScript 异步编程的根基,但是直接使用传统回调函数的方式,去完成复杂的异步流程,就无法避免大量的回调函数嵌套,这就会导致常说的回调地狱问题,为了避免回调地狱的问题 CommonJS 社区就提出了 Promise 规范,目的就是为 JavaScript 提供一种更合理、更强大的异步编程方案,后来在 es2015 中被标准化,称为语言规范。

// 回调地狱
fun(function(){
    fun1(function(){
        fun2(function(){
        	fun3(function(){
        		...
    		})
    	})
    })
})

new Promise()
    .then(function(){
    
	})
    .then(function(){
    
	})

所谓的 Promise 就是用一个对象表示异步任务结束之后是成功还是失败,就像是内部对外部做出了一个承诺,最开始承诺是待定状态(padding),最终可能成功(Fulfilled),也有可能失败(rejected),不管是达成还是失败,都会有相应的反应 onFulFilled、onRejected,在承诺结束之后都会有相应的任务被执行,而且还有一个明显的特点,就是一旦明确了结果就不可以被改变了

二、Promise的API及用法

2.1、api
  • Promise.prototype.then
  • Promise.prototype.catch
  • Promise.prototype.finally
  • Promise.resolve
  • Promise.reject
  • Promise.all
  • Promise.race
  • Promise.allSettled
2.2、实例方法
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 只会执行第一个resolve或者reject
        resolve(1);
        // reject(2);
    }, 2000);
}).then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
});
2.3、静态方法
  • 2.2.1 Promise.resolve 返回一个成功的Promise Promise.resolve 返回一个失败的Promise

    // 传入普通值
    Promise.resolve(1).then(value => {
        console.log(value);
    });
    // 传入成功的Promise对象
    Promise.resolve(new Promise((resolve, reject) => {
        resolve(1);
        // reject(1);
    })).then(value => {
        console.log(value);
    });
    
  • 2.2.2 Promise.all 只返回全部成功的结果,只要有一个失败则调用失败回调

    var p1 = new Promise(resolve => {
        resolve(2);
    }), p2 = new Promise((resolve, reject) => {
        resolve(3);
        // 只要有失败的就调用失败回调
        // reject(3);
    });
    var list = [1, p1, p2];
    Promise.all(list).then(value => {
        console.log(value);
    }, reason => {
        console.log(reason);
    });
    
  • 2.2.3 Promise.race 只返回速度最快的Promise,不管它是否成功

    var p1 = new Promise(resolve => {
        setTimeout(() => resolve(1), 3000);
    }), p2 = new Promise((resolve, reject) => {
        setTimeout(() => resolve(2), 2000);
    }), p3 = new Promise((resolve, reject) => {
        setTimeout(() => resolve(3), 1000);
        // setTimeout(() => reject(3), 1000);
    });
    
    var list = [p1, p2, p3];
    // 返回最快完成的Promise,不管是否成功
    Promise.race(list).then(value => {
        console.log("成功的" + value);
    }, reason => {
        console.log("失败的" + reason);
    });
    
  • 2.2.4 Promise.allSettled 调用成功回调,返回所有Promise的结果,不管里面有没有失败的

    var p1 = new Promise(resolve => {
        setTimeout(() => resolve(1), 3000);
    }), p2 = new Promise((resolve, reject) => {
        setTimeout(() => resolve(2), 2000);
    }), p3 = new Promise((resolve, reject) => {
        setTimeout(() => resolve(3), 1000);
        // setTimeout(() => reject(3), 1000);
    });
    
    var list = [p1, p2, p3];
    // 只返回所有Promise的结果,不管它是否成功
    Promise.allSettled(list).then(value => {
        console.log(value);
    });
    
2.4、错误捕获以及finally

Promise中会自动捕获错误,并调用then方法的失败回调,或者catch,无需使用try-catch捕获

// 
new Promise(resolve => {
    aaa
    resolve(1);
}).then(value => { }, reason => {
    console.log(reason);
}).catch(err => {
    console.log(err);
});
// finally中不管上面成功还是失败都一定会执行
new Promise((resolve, reject) => {
    // resolve(2);
    reject(1);
}).then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
}).finally(() => {
    console.log(1213);
});

三、了解Promise的原理

3.1、Promise的微任务队列

Snipaste_2021-08-07_15-50-46.png

3.2、微任务是什么时候创建呢?

​ 当一个padding状态的promise执行了resolve的方法时,我们传then方法的任务不会立即执行,而是把它注册到微任务队列中,

等待同步线程执行完毕,才会去执行微任务中的任务。而后面的then方法必须要等待上一个then方法执行完毕才会被添加到微任务队列中。

3.3、也许会在执行微任务时增加微任务,也就是微任务是按增加的顺序执行
new Promise(resolve => {
    console.log(1);
    resolve(2);
}).then(value => {
    console.log(value);
    return 3;
}).then(value => {
    console.log(value);
})

new Promise(resolve => {
    console.log(4);
    resolve(5);
}).then(value => {
    console.log(value);
});
3.3、一些常见面试题
  • 题目1
setTimeout(()=>{
    console.log(1)
},0);
Promise.resolve().then(()=>{
    console.log(2)
});
Promise.resolve().then(()=>{
    console.log(4)
});
console.log(3);
  • 题目2
setTimeout(() => {
    console.log(1)
}, 0)
new Promise((resolve) => {
    console.log(2)
    resolve()
}).then(() => {
    console.log(3)
}).then(() => {
    console.log(4)
})
console.log(5)
  • 题目3
const first = () => (new Promise((resolve, reject) => {
    console.log(3) // 同步执行 
    let p = new Promise((resolve, reject) => {
        console.log(7) // 同步执行 
        setTimeout(() => {
            console.log(5)  // 放到宏任务 5
            resolve(6)  // p的状态已经改变了一次 不会在改变了 所以6不会输出
        }, 0)
        resolve(1)  // 微任务 1
    })
    resolve(2) // 微任务 2
    p.then((arg) => {
        console.log(arg) 
    })
}))
first().then((arg) => {
    console.log(arg) 
})
console.log(4)   //同步执行的 

  • 题目4
setTimeout(() => {
    console.log("0")
}, 0)
new Promise((resolve,reject)=>{
    console.log("1") 
    resolve()
}).then(()=>{        
    console.log("2") 
    new Promise((resolve,reject)=>{
        console.log("3") 
        resolve()
    }).then(()=>{      
        console.log("4")
    }).then(()=>{       
        console.log("5")
    })
}).then(()=>{  
    console.log("6")
})

new Promise((resolve,reject)=>{
    console.log("7") 
    resolve()
}).then(()=>{         
    console.log("8")
})
3.2、Promise的运作流程

Snipaste_2021-08-06_19-00-08.png

3.3、实现一个非常简易的myPromise
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

class MyPromise {
    constructor(callback) {
        this._status = PENDING;
        this._value = undefined;
        this._fulfilled = [];
        callback(this._resolve.bind(this), this._reject.bind(this));
    }

    _resolve(value) {
        function run() {
            this._value = value;
            this._status = FULFILLED;
            let callback = null;
            while (callback = this._fulfilled.shift()) {
                callback(this._value);
            }
        }

        setTimeout(run.bind(this), 0);
    }

    _reject() {
		
    }

    then(fulfill, reject) {
        this._fulfilled.push(fulfill);
    }
}

四、async/await 关键字的使用

4.1、我们可以将普通函数声明为一个异步函数
async function fun() {
    return 1;
}
// 返回一个Promise对象
console.log(fun());

fun().then(value => {
    console.log(value);
});
4.2、使用await来等待一个Promise对象 (await只能在async修饰的函数中使用)
(async function () {

    let value = await new Promise(resolve => {
        resolve(2);
    });
    // await下面的语句必须等待await等待的Promise对象执行完毕才会执行
    console.log(value);

})();
4.3、使用场景
  • 串行调用 (适用于具有依赖性的异步操作)
function fun(v, time) {
    console.log("开始");
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(v1);
        }, time);
    });
}

(async function () {
    let val1 = await fun(1, 3000);
    console.log(val1);
    let val2 = await fun(2, 3000) + val1;
    console.log(val2);
})();
  • 平行调用 (适用于具有平行关系的异步操作)
function fun(v, time) {
    console.log("开始");
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(v1);
        }, time);
    });
}

(async function () {
    let p1 = fun(1, 3000);
    let p2 = fun(2, 3000);
    let val1 = await p1;
    let val2 = await p2;
    console.log(val1);
    console.log(val2);
})();
4.4、与Promise的区别
  • async/await相对于promise来讲,写法更加优雅
  • reject状态:
  • promise错误只可以通过catch或then来捕捉
  • async/await既可以用then又可以用try-catch捕捉
let p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('error');
    }, 1000);
});

async function demo(params) {
    try {
        let result = await p;
    } catch (e) {
        console.log(e);
    }
}

demo();