async-await & Promise & 回调地狱【2022-10-27 更新】

promise和async-await

看到这俩东西估计大家马上就说了,他们都能解决回调地狱的问题
这话是没错,但确实还没有说到本质上,这里先把还算完整的答案丢出来:

那什么是回调地狱呢?

回调地狱

简单说回调地狱就是:
一层函数里又套一层函数,像这样不停的套娃操作,一直要等到最里面的函数执行完以后外面的函数才能一层一层执行,顿时耳边回想起那句“当你在凝视深渊时,深渊也在凝视着你”…嗯~扯远了,不过呢套的层数多了就是这种感觉,先把直观的代码丢一份出来看看:

setTimeout(function () {
   console.log('第一层');
   setTimeout(function () {
       console.log('第二层');
       setTimeout(function () {
           console.log('第三层');
           setTimeout(function () {
	           console.log('第四层');
	           setTimeout(function () {
		           console.log('第五层');
		       }, 1000)
	       }, 1000)
       }, 1000)
   }, 1000)
}, 1000)

可以看到这份代码只回调了五层地狱,那如果是18层、180层地狱的话可想而知可读性…额~就没什么可读性可谈。可是为什么会有人写这种代码出来呢?
看代码可以发现 举例用的函数都是延时函数,众所周知延时函数是一项异步的任务

异步和同步: 同步就是运行这行代码时在主线程运行,异步就是当运行到这行代码时会把它由主线程交付给子线程去运行,而主线程继续运行后面的任务,等子线程运行完后再把结果返回给主线程

同异步可以简单理解为游戏里的主线任务和辅助任务,主线任务决定了游戏角色的等级,辅助任务可以帮助更快提升等级
为什么执行异步才会写出回调地狱,那就再简单说说js执行代码的顺序好了
具体内容还是需要了解一下 线程同异步任务事件循环(EventLoop)

js执行顺序: 简单一点来说就是在 Chrome 浏览器里有一个 V8 的js解析引擎,这个引擎在执行同步任务时会把同步任务放到主执行栈中,异步任务则会被加载到任务队列中,在主线程执行完当前同步代码后再去执行异步任务。

所以为了能让代码同步执行就只能一层一层嵌套起来,这就是回调地狱
于是为了解决回调地狱的问题Promise出现了

Promise

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知值的代理。它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

以上是 MDN 文档中的定义,并且一个 Promise 必然处于以下几种状态之一:

状态:

待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
已兑现(fulfilled): 意味着操作成功完成。
已拒绝(rejected): 意味着操作失败。

reject()

Promise.reject() 方法返回一个带有拒绝原因的 Promise 对象。
参数:reason:表示Promise被拒绝的原因。

// Promise.reject(reason)
Promise.reject(new Error('fail')) // 返回一个 rejected 状态的 Promise 对象
.then(function() {		// Promise 对象的状态为 fulfilled 时执行
  // not called
}, function(error) {	// Promise 对象的状态为 rejected 时执行
  console.error(error); // Stacktrace
});

resolve()

Promise.resolve(value) 方法返回一个以给定值解析后的 Promise 对象。如果这个值是一个 promise,那么将返回这个 promise;如果这个值是 thenable(即带有 “then” 方法),返回的 promise 会“跟随”这个 thenable 的对象,采用它的最终状态;否则返回的 promise 将以此值完成。此函数将类 promise 对象的多层嵌套展平。

var original = Promise.resolve(33);
var cast = Promise.resolve(original);
cast.then(function(value) {
  console.log('value: ' + value);
});
console.log('original === cast ? ' + (original === cast));

/*
*  打印顺序如下,这里有一个同步异步先后执行的区别
*  original === cast ? true
*  value: 33
*/

警告: 不要在解析为自身的 thenable 上调用Promise.resolve。这将导致无限递归,因为它试图展平无限嵌套的 promise。

错误演示: 会造成死循环

// thenable 即指一个自身带有then方法的对象
const thenable = {
  then: (resolve, reject) => {
    resolve(thenable)
  }
}

Promise.resolve(thenable) //这会造成一个死循环

then() 方法

再说一说 Promise 中的静态方法 Promise.prototype.then() — — — > Promise.prototype.then()-MDN文档

Promise.prototype.then() 方法返回一个 Promise 对象。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

// 构造一个 Promise实例(对象)
const p = new Promise()

// p.then(onFulfilled[, onRejected]);

p.then(value => {
  // fulfillment:
  // 此时 p 的状态为 fulfilled
  console.log('操作成功!')
}, reason => {
  // rejection:
  // 此时 p 的状态为 rejected
  console.error('操作失败!')
});
参数:

onFulfilled (可选)
当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
onRejected (可选)
当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。如果该参数不是函数,则会在内部被替换为一个 “Thrower” 函数 (it throws an error it received as argument)。

onFulfilled 和 onRejected 都是回调函数,区别只在于:

  • onFulfilled 回调函数只在 Promise 的状态是已兑现(fulfilled) 时,或者说操作成功时,执行回调里面的代码。
  • onRejected 回调函数只在 Promise 的状态是已拒绝(rejected) 时,或者说操作失败时,执行回调里面的代码。
返回值:

当一个 Promise 完成(fulfilled)或者失败(rejected)时,返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回。如果 then 中的回调函数:
1、返回了一个值,那么 then 返回的 Promise 将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
2、没有返回任何值,那么 then 返回的 Promise 将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
3、抛出一个错误,那么 then 返回的 Promise 将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
4、返回一个已经是接受状态的 Promise,那么 then 返回的 Promise 也会成为接受状态,并且将那个 Promise 的接受状态的回调函数的参数值作为该被返回的 Promise 的接受状态回调函数的参数值。
5、返回一个已经是拒绝状态的 Promise,那么 then 返回的 Promise 也会成为拒绝状态,并且将那个 Promise 的拒绝状态的回调函数的参数值作为该被返回的 Promise 的拒绝状态回调函数的参数值。
6、返回一个未定状态(pending)的 Promise,那么 then 返回 Promise 的状态也是未定的,并且它的终态与那个 Promise 的终态相同;同时,它变为终态时调用的回调函数参数与那个 Promise 变为终态时的回调函数的参数是相同的。

简单来说 then() 方法的返回的就是一个Promise对象, 只不过这个Promise对象的状态需要分两种情况:

  1. 在回调函数中返回
  • 回调函数中返回的是一个具体值或者没有返回值的话,then() 方法返回的Promise对象的状态为
    已兑现(fulfilled)
  • 如果返回具体值则这个具体的值会成为 返回的 fulfilled 状态下的 Promise 在第一个回调函数中的参数
  • 如果在回调函数中抛出一个错误的话,then() 方法返回的Promise对象的状态为 已拒绝(rejected)
// 构造一个 Promise实例(对象)
const p = new Promise()
const p2 = new Promise((resolve, reject) => {
  resolve('fulfilled');
})

const result = p.then(success => {
  return p2
}, fail => {
});

console.log(result) // 一个 Promise 实例(对象)并且其状态为 fulfilled

  1. 在回调函数中返回 Promise 实例(对象)
  • 回调函数中返回的 Promise 实例(对象)的状态 和 then() 调用后返回的 Promise 实例(对象)的状态是相同的。
    具体的:
    如果回调中是 fulfilled 那 then()返回的也是 fulfilled
    如果回调中是 rejected 那 then()返回的也是 rejected
    如果回调中是 pending 那 then()返回的也是 pending

举个返回 fulfilled 的 Promise 的例子

// 构造一个 Promise实例(对象)
const p = new Promise()
const p2 = new Promise((resolve, reject) => {
  resolve('fulfilled');
})

const result = p.then(success => {
  return p2
}, fail => {
});

console.log(result) // 一个 Promise 实例(对象)并且其状态为 fulfilled

不怕啰嗦,再来个 rejected 的例子

// 构造一个 Promise实例(对象)
const p = new Promise()
const p2 = new Promise((resolve, reject) => {
  reject('rejected');
})

const result = p.then(success => {
  return p2
}, fail => {
});

console.log(result) // 一个 Promise 实例(对象)并且其状态为 rejected

来个复杂一点的

const p1 = new Promise((resolve, reject) => {
  resolve('fulfilled');
})
const p2 = new Promise((resolve, reject) => {
  reject('rejected');
})

const result = p2.then(success => {
}, fail => {
  console.log(fail) // 'rejected'
  return p1
});

console.log(result) // 一个 Promise 实例(对象)并且其状态为 fulfilled

链式调用

Promise中的 then()方法如何实现链式调用的:
因为.then()方法每次调用后都会返回一个Promise对象

const p = new Promise((resolve, reject) => {
  resolve(1)
})

p.then(success => {
  console.log(success) // 1
  return ++success
}, fail => {
})
.then(success => {
  console.log(sucess) // 2
  return Promise.reject('error')
}, fail => {
}).catch(e => {
  // 通过使用 catch 方法捕获异常后可以继续使用链式
  console.log(e) // 'error'
})
.then(() => {
  console.log('fulfilled')
}, () => {
  console.log('rejected')
})

通过链式调用可以看出 Promise 虽然是解决了像箭头一样的回调地狱问题但是这样去写可读性也并没有好到哪去,于是后来为了完善Promise还不够完美的地方,出现了 async-await 的语法。

async-await

async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 promise。

后续再更新…

你可能感兴趣的:(原生js的面试痛点,前端,javascript)