概述
Promise 是异步编程的一种解决方案,比传统的解决方案—回调函数和事件—更合理和更强大。
- Promise是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
- 语法上,Promise 是一个对象,从它可以获取异步操作的消息。
- Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理
Promise对象的特点
- 对象的状态不受外界影响。
Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
Promise的缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
console.log("a");
setTimeout(() => console.log("b"));
let p = new Promise((resolve, reject) => {
resolve();
console.log("c");
}).then(() => {
console.log("d");
});
console.log("e");
// a c e b d
由以上的输出顺序我们可以引出一个概念:宏任务和微任务
宏任务和微任务
每个“线程”都有自己的事件循环。因此,每个Web工作者都有自己的,所以可以独立执行。而同一来源上的所有窗口都共享一个事件循环,因为它们可以同步通信。事件循环持续运行,执行排队的任何任务。事件循环具有多个任务源,可以保证该源中的执行顺序(规范)。
微任务通常对当前执行脚本之后应该发生的事情进行调度,例如响应一批操作,或者在不承担整个新任务的代价的情况下进行异步操作。只要在执行过程中没有其他JavaScript,并且在每个任务结束时,微任务队列就会在回调后进行处理。在微任务期间排队的任何其他微任务都会添加到队列的末尾,并进行处理。
宏任务包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O。
微任务包括: Promises, process.nextTick, Object.observe。
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
Promise.resolve().then(()=>{
console.log('1')
setTimeout(()=>{
console.log('2')
},0)
})
setTimeout(()=>{
console.log('3')
Promise.resolve().then(()=>{
console.log('4')
})
},0)
// 1 3 4 2
1(红色):JS 引擎会把微观任务Promise存入执行栈,把宏观任务setTimeout存入 “任务队列”
2(绿色):主线程率先运行执行栈中的代码,依次输入1,然后把绿框的setTimeout存入 “任务队列”
3(蓝色):执行栈清空以后,会率先读取 “任务队列” 中最早存入的setTimeout(红框的那个),并把这个定时器存入栈中,开始执行。这个定时器中的代码都是微观任务,所以可以一次性执行,依次输出3 和 4
4(紫色):重复第3步的操作,读取 “任务队列” 中最后存入的setTimeout(绿框的那个),输出2
该代码示例及图片来源:https://blog.csdn.net/jiaojsun/article/details/95035626
Promise的基本用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
const promise = new Promise(function (resolve, reject) {
//...do something
if (/*异步操作成功*/) {
resolve(value);
} else {
reject(error);
}
});
- Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript引擎提供,不用自己部署。
- resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
- reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
promise.then(function(value){
//value
}, function(error){
//error
});
then方法可以接受两个回调函数作为参数。
- 第一个回调函数是Promise对象的状态变为resolved时调用。
- 第二个回调函数是Promise对象的状态变为rejected时调用。
- 其中,第二个函数是可选的,不一定要提供。
- 这两个函数都接受Promise对象传出的值作为参数。
Promise 新建后就会立即执行。
let promise = new Promise(function (resolve, reject) {
console.log(`Promise`);
resolve();
});
promise.then(function () {
console.log("resolved");
});
console.log("Hi");
// Promise Hi resolved
- Promise新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
调用resolve函数和reject函数时带有参数,那么这些参数会传递给回调函数。
- reject函数的参数通常是Error对象的实例,表示抛出的错误;
- resolve函数的参数除了正常的值以外,还可能是另一个Promise实例;
var p1=new Promise((resolve, reject)=>{
//...
});
var p2=new Promise((resolve, reject)=>{
//...
resolve(p1);
});
- 此时,p1的状态就会传递给p2。
- p1的状态决定了p2的状态,如果p1为Pending,那么p2的回调函数就会等待p1的状态改变;如果p1的状态为resolved或rejected,那么p2的回调将会立即执行。
链式调用
Promise实例可以链式的then( )调用,并且可以一直调用下去(如果需要)。
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
let t = true;
if (t) {
resolve("success");
} else {
reject("failed");
}
}, 1000);
});
promise1.then((r) => {
console.log(r);
return r + "1";
}).then((r) => {
console.log(r);
return r + "2";
}).then((r) => {
console.log(r);
});
通常,遇到异常抛出,会顺着promise链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数。
new Promise((resolve, reject) => {
console.log("in Promise");
resolve();
}).then(() => {
throw new Error("then Error");
console.log("then");
}).then(() => {
console.log("then continue");
}).catch((err) => {
console.log("catch error:" + err.message);
});
建议:利用catch来终止promise链,避免链条中的rejection抛出错误到全局。
当需要将多个Promise任务一起执行时,可以使用Promise.all( )方法。
- Promise.all( )实参是所有Promise实例的字面量组成的数组,执行完毕的结果是所有输出结果的所组成的数组。
var p1 = new Promise((res, rej) => {
setTimeout(() => {
res("p1");
}, 1000);
});
var p2 = new Promise((res, rej) => {
setTimeout(() => {
res("p2");
}, 2000);
});
var p3 = new Promise((res, rej) => {
setTimeout(() => {
res("p3");
}, 3000);
});
Promise.all([p1, p2, p3]).then((r) => {
console.log(r);
}).catch((err) => console.log(err.messsage));
// [ 'p1', 'p2', 'p3' ]
async/await
ES2017引入了async函数,使异步操作变得更加方便。
- async函数返回的Promise对象会运行执行(resolve)异步函数的返回结果,或者如果异步函数抛出异常的话会运行拒绝(reject)。
async function testAsync() {
return "hello async";
}
console.log(testAsync());
- async会把返回值传递给Promise.resolve( )。
testAsync().then((r) => {
console.log(r);
});
异步函数可以包含await指令,该指令会暂停异步函数的执行,并等待Promise执行,然后继续执行异步函数,并返回结果。
- 如果等待的不是 Promise 对象,则返回该值本身。
- await 关键字只在异步函数内有效。
function testAsync() {
//只有返回值、没有返回Promise的函数
return "hello async";
} function testPromise() {
//返回Promise的函数
return new Promise((resolve) => {
resolve("hello Promise");
});
} async function test() {
var Async_1 = await testAsync();
var Promise_1 = await testPromise().then();
console.log(Async_1); //输出 hello async
console.log(Promise_1); //输出 hello Promise
}
test();
// hello async hello Promise
async/await总结
总结来源:https://juejin.cn/post/6844903507938541581
Async:定义异步函数
- 自动把函数转换为 Promise
- 当调用异步函数时,函数返回值会被 resolve 处理
- 异步函数内部可以使用 await
Await - 暂停异步函数的执行
- 当使用在 Promise 前面时,await 等待 Promise 完成,并返回 Promise 的结果
- await 只能和 Promise 一起使用,不能和 callback 一起使用
- await 只能用在 async 函数中
Async/Await 底层依然使用了 Promise。
多个异步函数同时执行时,需要借助 Promise.all
async function getABC() {
let A = await getValueA(); // getValueA 花费 2 秒
let B = await getValueB(); // getValueA 花费 4 秒
let C = await getValueC(); // getValueA 花费 3 秒
return A*B*C;
}
每次遇到 await 关键字时,Promise 都会停下在,一直到运行结束,所以总共花费是 2+4+3 = 9 秒。await 把异步变成了同步
在 Async/Await 语法中,我们可以使用 try/catch 进行错误处理。在 Promise 中的 .catch() 分支会进入 catch 语句。