异步编程的解决方案(执行代码的先后顺序:1.主任务,任务队列:2.微任务,3.宏任务)Promise/Generator/async

Promise 主要是换种写法而已 (把异步编程变成同步的一种写法)  英文是‘’承诺‘’的意思,表示其它手段无法改变。

三种状态::pending(进行中)、fulfilled(已成功)和rejected(已失败),只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是Promise这个名字的由来。

Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected,只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型),如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果,这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意:只有 Promise 体内的代码是同步进行的。      Promise:(低版本用不了)

pending :进行中

resolve : 决定(成功)

reject :拒绝(失败)

finally :成功和失败都会执行(非Promise自带,需自行扩展)

done:会捕捉到任何可能出现的错误,并向全局抛出。(非Promise自带,按心情自行扩展)

一. new Promise((resolve,reject)=>{ resolve( ); reject( ) })

 1     let str = 10;
 2     //p是 <promise> 对象 可以进行链式调用,链式调用的返回值是 this (这里指向p,所以可以一直使用 .then( ) 的方法)
 3     let p = new Promise(function (resolve, reject) {
 4       setTimeout(function () {
 5         str = 20;
 6         resolve(str)
 7       }, 100);
 8     });
 9 
10     p.then((str) => { //这里拿到的 str 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 str
11       console.log(str) //20
12       return 5; // 虽然写了 return,但返回的依然是一个新的 promise 对象,而这里的 return 是给下一个.then 传参。
13     }).then((a) => {
14       console.log(a) //5
15     });
16 
17     p.then((xxx) => { //这里拿到的 xxx 是 new 里面的 resolve(str) 里的 str 相当于这里的函数,在上面被调用,并传实参 xxx
18       console.log(xxx) //20
19     })
// 先打印 11行的 20, 再输出 18行的 20, 最后输出 14行的 5, 因为 11行 和 18行, 都是最先被执行的 promise, 而14行是一个新的 promise,属于新的微任务,进入异步队列

 

二. 哪里可以拿到 reject 中的内容

reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch,没有 then 也会进入 catch

 var p1=new Promise((resolve,rej) => {
    console.log('没有resolve')
    //throw new Error('手动返回错误')
    rej('失败了')
 
 })
 
 p1.then(data =>{
    console.log('data::',data);
 },err=> {
    console.log('err::',err)
 }).catch(
    res => {
    console.log('catch data::', res)
 })
 —————结果————————
  没有resolve
  err:: 失败了

then中没有第二个回调的情况

 var p1=new Promise((resolve,rej) => {
    console.log('没有resolve')
    //throw new Error('手动返回错误')
    rej('失败了')
 
 })
 
 p1.then(data =>{
    console.log('data::',data);
 }).catch(
    res => {
    console.log('catch data::', res)
 })
———————结果———————
没有resolve
catch data:: 失败了

同理: resolve的东西,一定会进入then的第一个回调,肯定不会进入catch

三. Promise.all ( [ p1 , p2 , ...... ] )

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。(all会将传入的数组中的所有promise全部决议以后,将决议值以数组的形式传入到观察回调中,任何一个promise决议为拒绝,那么就会调用拒绝回调。)

let p1 = new Promise((resolve, reject) => {
  resolve('成功了')
})

let p2 = new Promise((resolve, reject) => {
  resolve('success')
})

let p3 = Promse.reject('失败')

Promise.all([p1, p2]).then((result) => {
  console.log(result)               //['成功了', 'success']
}).catch((error) => {
  console.log(error)
})

Promise.all([p1,p3,p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)      // 失败了,打出 '失败'
})

Promse.all在处理多个异步处理时非常有用,比如说一个页面上需要等两个或多个ajax的数据回来以后才正常显示,在此之前只显示loading图标。

代码模拟:

let wake = (time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${time / 1000}秒后醒来`)
    }, time)
  })
}

let p1 = wake(3000)
let p2 = wake(2000)

Promise.all([p1, p2]).then((result) => {
  console.log(result)       // [ '3秒后醒来', '2秒后醒来' ]
}).catch((error) => {
  console.log(error)
})
需要特别注意的是,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。
这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。

四. Promise.race的使用

顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  },1000)
})

let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('failed')
  }, 500)
})

Promise.race([p1, p2]).then((result) => {
  console.log(result)
}).catch((error) => {
  console.log(error)  // 打开的是 'failed'
})

五. Promise是如何捕获异常的?与传统的try/catch相比有什么优势?

传统的 try / catch 捕获异常方式是无法捕获异步的异常的。

而对于 Promise 对象来说,构造 Promise 实例时的代码如果出错,则会被认为是一个拒绝的决议,并会向观察回调中传递异常信息。所以即使是一个异步的请求,Promise 也是可以捕获异常的。此外,Promise 还可以通过 catch 回调来捕获回调中的异常。

六.如果向Promise.all()和Promise.race()传递空数组,运行结果会有什么不同?

all会立即决议,决议结果是fullfilled,值是undefined

race会永远都不决议,程序卡住……

七.扩展一个 Promise.finally()

扩展一个 Promise.finally(),finally方法用于指定不管Promise对象最后状态如何,都会执行的操作.

它与done方法的最大区别在于,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

//添加finally方法
Promise.prototype.finally=function (callback) {
   var p=this.constructor;
   return this.then(//只要是promise对象就可以调用then方法
     value => p.resolve(callback()).then(() => value),
     reason => p.resolve(callback()).then(() => {throw reason})
   );
}

对finally方法的理解:

  (1) p.resolve(callback())这句函数callback已经执行
  (2) finally方法return的是一个promise对象,所以还可以继续链式调用其他方法
  (3) 对于Promise.resolve方法: Promise.resolve('foo') 等价于 new Promise(resolve => resolve('foo'));

    所以可以通过then方法的回调函数 接受 实例对象返回的参数
    比如: Promise.resolve(function(){console.log(2);}).then(function(cb){cb()})

  (4) p.resolve(callback()).then(() => value)调用then的目的是给promise实例即this添加成功和失败的回调函数

Promise.finally() 的使用方法:

  Promise.finally(),按照以上的扩展方式

八. 扩展一个 Promise.done()

Promise 对象的回调链,不管以then方法或catch方法结尾,要是最后一个方法抛出错误,都有可能无法捕捉到(因为 Promise内部的错误不会冒泡到全局)。因此,我们可以提供一个done方法,总是处于回调链的尾端,保证抛出任何可能出现的错误。
  Promise.prototype.done = function (onFulfilled, onRejected) {
    this
      .then(onFulfilled, onRejected)
      .catch(function (reason) {
        // 抛出一个全局错误
        setTimeout(() => {
          throw reason
        }, 0)
      })
  }
从上面代码可见,done方法的使用,可以像then方法那样用,提供fulfilledrejected状态的回调函数,也可以不提供任何参数。但不管怎样,done都会捕捉到任何可能出现的错误,并向全局抛出。

执行代码的先后顺序:

  先 主任务 后 任务队列。

    任务队列又分:宏任务(task)、微任务(Microtasks)

      微任务(Microtasks)-> Promise 、process.nextTick

      宏任务(task)-> 定时器(比如:setTimeout)、事件(比如:onclick)、整体代码script

任务队列既有宏任务又有微任务,先执行微任务,再执行宏任务。(1.主任务(主线程)-> 2. 微任务 -> 3.宏任务)。这种循环机制叫做任务循环(event loop)

 


基于 Promise 之后。又有 Generator ,和它的语法糖:async 

Generator:函数是协程在ES6的实现,最大的特点就是可以交出函数的执行权(即暂停执行),交给 yield 后面的 语句,等此语句执行完,再执行下面的代码。

协程:

  第一步:协程 开始执行。

  第二步:协程 执行到一半,进入暂停,执行权转移到协程 B

  第三步:(一段时间后)协程 B 交还执行权

  第四步:协程 A 恢复执行。

也就是说 Promise 和  Generator 一个明显不同的特点是,前者只在Promise体内进行异步代码的同步执行,(不会阻断主任务)

而后者是阻断(主任务)的执行,等到异步的代码执行完毕之后再执行被阻断的(主任务)代码。

function* gen(x){
//这个函数体内任何地方console.log(y)都是undefined
  var y = yield x + 2 ;
  var z = yield x + y ;
  return y;
};
let y = gen(5);
console.log(y.next ()) //  让代码继续执行,并 返回值 拿到 return 后面的 y -> {value: 7, done: false}
y.next(y.next ( ).value)  //next中可以传入数据(传入的数据为上一次yield表达式的返回值(就是这里的y)),为了下一步操作的运算。

async:(**现在最常用)

function fn2(){
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(50);
        },1000);
    })
}

async function fn(){
    let y = await fn2() + 2;
    console.log(y);
}
fn();//调用后,输出 52 。要 等待 fn2执行完(将异步转成同步的感觉)

 主线程-微任务-宏任务 练习:(如何使用async、await、Promise以及各种任务的执行特点)

async function fn() {
  console.log(1);
  let b = await fn2();//b拿到return的值 = resolve()里面的值,并阻塞下面的代码。
  console.log(b);
  console.log(2)//出现在await之后,也就是2一定跟着3之后出现
};
function fn2() {
  return new Promise((resolve, reject) => {//async、await要配合Promise使用。
      console.log(9);
      setTimeout(() => {
          resolve(3)//进入宏任务,定时器谁先触发谁先执行。
      }, 1000)
  });
}
// async function fn() {
//     //function fn() {
//     console.log(1);
//     let b = await fn2();
//     console.log(2)//进入微任务,谁先进入谁先触发。
// };
// function fn2() {
//     console.log(9);
//     setTimeout(() => {
//         resolve(3)//进入宏任务,定时器谁先触发谁先执行。
//     }, 1000)
// };

setTimeout(() => {
  console.log(6)//进入宏任务
}, 500);
fn()
let a = new Promise((resolve, reject) => {
  console.log(4);
  resolve()
})
a.then(() => {
  console.log(5)//进入微任务
});
//fn()
console.log(8);
//1,9,4,8,2,5,3,6
//4,1,9,8,5,2,3,6
//注:如果await后面的函数,没有被Promise包着。 await 下面的代码会进微任务队列(谁先进入谁先执行),定时器会进宏任务队列(谁先触发谁先执行)。
//1,9,4,8,5,6,3,2
//await后面的函数被Promise包着,那么await后面的函数才会阻塞下面的函数,等await后面的函数执行完,再往下执行(只在函数体内发生)。
原文地址:https://www.cnblogs.com/MrZhujl/p/10095875.html