异步

107 阅读4分钟

异步行为是为了优化因计算量大而时间长的操作,如果在等待其它操作完成的同时,可以执行其它指令,对于js这种单线程事件循环模型中是非常有效的。重要的是,异步操作并不是特指那些需要花费大量时间的操作,任何时候只要你不想为等待某个异步操作而阻塞线程执行都可以使用

Promise

在早期js中异步是靠回调函数实现的,但当逻辑变得复杂时就容易产生回调地狱问题,使得代码变得难以理解。所以为了能更好的解决异步编程问题就提出来Promise的概念

期约

期约是对尚不存在结果的一个替身。ES6新增Promise

let p = new Promise(()=>{})

Promise有三种状态

  • pending 待兑现
  • resolved、fulfilled 已兑现
  • rejected 已拒绝

promise状态只能从一种状态变成另一种状态(pending-->resolved、pending-->rejected)或者永远处于pending

修改状态

由于promise的状态的私有的,只能通过内部的执行函数才能进行修改

const p1 = new Promsie((resolve,reject)=>{
  resolve() // 执行resolve,promise状态pending-->resolved
})

const p2 = new Promsie((resolve,reject)=>{
  reject() // reject,promise状态pending-->rejected
})
  1. 执行函数是同步的
  2. 执行函数只能接收一个参数

Promise.resolve()

promise并不一定就是pending,通过Promise.resolve可以得到一个resolved的期约

const p1 = new Promsie((resolve,reject)=>{
  resolve()
})
const p2 = Promise.resolve()

这两个期约实际上是一样的

Promise.reject()

同理可以通过Promise.reject得到一个rejected的期约

实例方法

Promise的实例方法是内部异步与外部同步的桥梁,

then

then最多接收两个方法onResolved、onRejected,分别在rosolved、rejected时执行

function onResolved(){
  console.log('onResolved')
}

function onRejected(){
  console.log('onRejected')
}

const p1 = new Promsie((resolve,reject)=>{
  resolve()
}).then(onResolved,onRejected) // 打印onResolved

const p2 = new Promsie((resolve,reject)=>{
  reject()
}).then(onResolved,onRejected) // 打印onRejected

同时then方法会返回一个新期约,这是为了确保链式调用(chaining)能够正确地进行,并且每个 then 方法的处理结果(无论是正常解决还是拒绝)都可以被进一步处理

  1. 如果回调函数返回一个值(无论是普通值、对象还是另一个 Promise):
  • 新 Promise 会被解决(fulfilled)为这个返回值。如果返回值是另一个 Promise,则新 Promise 的状态会“跟随”那个返回的 Promise。
  1. 如果回调函数抛出一个错误:
  • 新 Promise 会被拒绝(rejected)为抛出的那个错误
let promise1 = new Promise((resolve, reject) => { resolve("成功"); }); 

let promise2 = promise1.then(value => {
console.log(value); // 输出: 成功 
return "链式调用的结果"; 
});

promise2.then(newValue => { 
console.log(newValue); // 输出: 链式调用的结果 
});

// 也可以处理错误 
promise1.then(value => { 
throw new Error("发生错误");
}).catch(error => { 
console.error(error); // 输出: 发生错误
});

catch

catch用于捕获Promise中的错误,返回一个新的promsie。实际上这个就是一个语法糖,调用它就相当于调用Promise.prototype.then(null, onRejected)

let p = Promise.reject(); 
let onRejected = function(e) { 
  setTimeout(console.log, 0, 'rejected'); 
}; 
  
// 这两种添加拒绝处理程序的方式是一样的:
p.then(null, onRejected); // rejected 
p.catch(onRejected); //rejected

finally

finally方法无论是resolved还是rejected都会执行,但finally并不知道Promise的状态,主要用于做一些清理工作

const p1 = Promise.resolve()
const p2 = Promise.reject()
function onFinally(){
  setTimeout(console.log,0,'Finally')
}

p1.finally(onFinally)  // Finally
p2.finally(onFinally)  // Finally

执行finally会返回一个新Promise。这个新Promise不同于then()或catch()方式返回的实例。因为onFinally被设计为一个状态无关的方法,所以在大多数情况下它将表现为父期约的传递

const p1 = new Promise(()=>{})
const p2 = p1.finally()

setTimeout(console.log,0,p1) // Promise <pending> 
setTimeout(console.log,0,p2) // Promise <pending> 
setTimeout(p1 === p2) false

Promise.all()和Promise.race()

当我们需要一次处理多个异步时,promise提供了Promise.all()和Promise.race()供我们使用,但他们在使用上又有所不同。

Promise.all()

Promise.all接收一个数组,必须等待所有期约完成才算完成,只要有一个拒绝整个状态就变成拒绝了。有点类似合作关系

const p1 = Promise.resolve()
const p2 = Promise.resolve()
const A1 = Promise.all([p1,p2])
setTimeout(console.log,0,A1) // Promise <fulfilled>


const p3 = Promise.resolve()
const p4 = Promise.reject()
const A2 = Promise.all([p3,p4])
setTimeout(console.log,0,A2) // Promise <rejected>

Promise.all()必须接收一个参数,否则就会报错

Promise.all()

Promise.all接收一个数组,整个期约的状态由第一个最先被resolved或rejected的决定。传入的期约之间属于竞争关系

const p1 = Promise.race([
Promise.resolve(),
new Promise((resolve,reject)=>setTimeout(reject,1000))
])
setTimeout(console.log,0,p1) // Promise <fulfilled>


const p2 = Promise.race([
Promise.reject(),
new Promise((resolve,reject)=>setTimeout(resolve,1000))
])
setTimeout(console.log,0,p2) // Promise <rejected>