promise,async, await,事件循环

76 阅读11分钟

1.预备知识

1.1 实例对象与函数对象

  • 实例对象:new 函数产生的对象,称为实例对象,简称为对象
  • 函数对象:将函数作为对象使用时,称为函数对象
function Fn() { // Fn只能称为函数
}
const fn = new Fn() // Fn只有new过的才可以称为构造函数
//fn称为实例对象
console.log(Fn.prototype)// Fn作为对象使用时,才可以称为函数对象
Fn.bind({}) //Fn作为函数对象使用
$('#test') // $作为函数使用
$.get('/test') // $作为函数对象使用

  • ()左边是函数,点左边是对象(函数对象、实例对象)

1.2 2种回调函数

    1. 同步回调

      立即执行,完全执行完了才结束,不会放入回调队列中

      如:数组遍历相关的回调 / [Promise])的executor函数

 const arr = [1, 3, 5]; 
 arr.forEach(item => {console.log(item); })   // // 遍历回调,同步回调,不会放入队列,一上来就要执行 
 console.log('forEach()之后')

image.png

  • 2.异步回调

       不会立即执行,会放入回调队列中将来执行
    

如:定时器回调 / ajax回调 / Promise成功或失败的.then 异步的回调

 
```js
 定时器回调 setTimeout(() => {  console.log('timeout callback()') }, 0)     //// 异步回调,会放入队列中将来执行
 console.log('setTimeout()之后')

image.png

// Promise 成功或失败的回调
new Promise((resolve, reject) => {
  resolve(1)
}).then(
  value => {console.log('value', value)},
  reason => {console.log('reason', reason)}
)
console.log('----')

// ----
// value 1

.then 是异步的,

1.3 Promise是什么?

  1. Promise 是一门新的技术(ES6 规范)
  2. Promise 是 JS 中进行异步编程的新解决方案 备注:旧方案是单纯使用回调函数
  3. 从语法上来说: Promise是一个构造函数 (自己身上有allrejectresolve这几个方法,原型上有thencatch等方法)
  4. 从功能上来说: promise 对象用来封装一个异步操作并可以获取其成功/ 失败的结果值

2. promise 的状态

  1. 实例对象promise中的一个属性 PromiseState
  • pending 未决定的
  • resolved/fullfilled 成功
  • rejected 失败

  • pending 变为 resolved/fullfilled
  • pending 变为 rejected
  • 注意:状态的改变只有2种,-且一个 promise 对象只能改变一次
  • 一旦状态改变,就不会再变
  • 无论成功还是失败,都会有一个结果数据。成功的结果数据一般称为 value,而失败的一般称为 reason
const promise = new Promise((resolve, reject) => {
                setTimeout(() => {
                    reject('失败')
                    resolve('成功')
                }, 3000);
                })
promise.then(res => console.log(res)).catch(err => console.log(err)

  
 当我调用`reject`之后,在调用`resolve`是无效的,因为状态已经发生改变,并且是不可逆的。


  1. Promise对象的值
  • 实例对象promise的另一个值 PromiseResult
  • 保存着对象 成功/失败 的值(value/reason

2.1 resolve不同值的区别

  • 如果resolve传入一个普通的值或者对象,只能传递接受一个参数,那么这个值会作为then回调的参数
 const promise = new Promise((resolve, reject) => {
                 resolve({name: 'ice', age: 22})
        })

        promise.then(res => console.log(res))
  • 如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态
 const promise = new Promise((resolve, reject) => {
        resolve(new Promise((resolve, reject) => {
            setTimeout(() => {
            resolve('ice')
            }, 3000);
        }))
        })

        promise.then(res => console.log(res))

   //3s后 ice

3. Promise 的基本流程

image.png

4.为什么要用 Promise

5.Promise中的常用 API 概述

一 Promise 构造函数: Promise (excutor) {}

  • executor 函数: 执行器 (resolve, reject) => {}
  • resolve 函数: 内部定义成功时我们调用的函数 value => {}
  • reject 函数: 内部定义失败时我们调用的函数 reason => {}
  • 说明: executor 会在 Promise 内部立即同步调用
  • 异步操作 resolve/reject 就在 executor 中执行
new Promise((resolve, reject) => { console.log(`executor 立即执行`) })

 传入的`executor`是立即执行的
 打印'executor 立即执行'
 executor 指的是(resolve, reject) => { console.log(`executor 立即执行`) }

5.1 Promise的实例方法

  1. 实例方法,存放在Promise.prototype上的方法,也就是Promise的显示原型上,当我new Promise的时候,会把返回的改对象的 promise[[prototype]](隐式原型) === Promise.prototype (显示原型)
  2. 即new返回的对象的隐式原型指向了Promise的显示原型

5.2 Promise.resolve 方法:Promise.resolve(value)

value:将被 Promise 对象解析的参数,也可以是一个成功或失败的 Promise 对象

说明:返回一个成功或失败的promise对象

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

           let p2 = Promise.resolve(new Promise((resolve, reject) => {
                // resolve('OK'); // 成功的Promise
                reject('Error');
            }));
            console.log(p2);
            p2.catch(reason => {
                console.log(reason);
            })
     
     此参数为失败的promise对象,返回的也是失败的promise对象,则参数的结果决定了 resolve 的结果
            

(2)参数是一个thenable对象 thenable对象指的是具有then方法的对象,比如下面这个对象。

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

Promise.resolve()方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法

let thenable = {
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function (value) {
  console.log(value);  // 42
});

上面代码中,thenable对象的then()方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then()方法指定的回调函数,输出42。

(3)参数不是具有then()方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then()方法的对象,则Promise.resolve()方法返回一个新的 Promise 对象,状态为resolved

const p = Promise.resolve('Hello');

p.then(function (s) {
  console.log(s)
});
// Hello

(4)不带有任何参数

Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。

所以,如果希望得到一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve()方法。

const p = Promise.resolve();

p.then(function () {
  // ...
});
上面代码的变量`p`就是一个 Promise 对象。

5.3 Promise.reject 方法

说明:返回一个失败的 promise 对象

        let p = Promise.reject(521);
        let p2 = Promise.reject('iloveyou');
        let p3 = Promise.reject(new Promise((resolve, reject) => {
            resolve('OK');
        }));

        console.log(p);
        console.log(p2);
        console.log(p3);

image.png

更多例子:

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了
上面代码生成一个 Promise 对象的实例`p`,状态为`rejected`,回调函数会立即执行。
Promise.reject('出错了')
.catch(e => {
  console.log(e === '出错了')
})
// true
上面代码中,`Promise.reject()`方法的参数是一个字符串,后面`catch()`方法的参数`e`就是这个字符串。
  • Promise.resolve() / Promise.reject() 方法就是一个语法糖
  • 用来快速得到Promise对象

5.4 then方法

  • then方法可以接受参数,一个参数为成功的回调,另一个参数为失败的回调
   const promise = new Promise((resolve, reject) => {
        resolve('request success')
        // reject('request error')
        })

        promise.then(res => console.log(res), rej => console.log(rej))
        //request success
  • 如果只捕获错误,还可以这样写:- 因为第二个参数是捕获异常的,第一个可以写个null""占位
   const promise = new Promise((resolve, reject) => {
        // resolve('request success')
        reject('request error')
        })

    promise.then(null, rej => console.log(rej))
    //request error
  • then的多次调用
      const promise = new Promise((resolve, reject) => {
        resolve('hi ice')
        })

        promise.then(res => console.log(res))
        promise.then(res => console.log(res))
        promise.then(res => console.log(res))
  • then的返回值
    then方法是有返回值的,它的返回值是promise

5.5 Promise.all 方法:Promise.all(iterable)

iterable:包含 n 个 promise 的可迭代对象,如 Array 或 String

说明:返回一个新的 promise,只有所有的 promise 都成功才成功,只要有一个失败了就直接失败

       let p1 = new Promise((resolve, reject) => {
        resolve('OK');
        })
        let p2 = Promise.resolve('Success');
        let p3 = Promise.resolve('Oh Yeah');

        const result = Promise.all([p1, p2, p3]);
        console.log(result);

image.png

 let p1 = new Promise((resolve, reject) => {
  resolve('OK');
})
let p2 = Promise.reject('Error');
let p3 = Promise.resolve('Oh Yeah');

const result = Promise.all([p1, p2, p3]);
console.log(result);

image.png

 const p1 = Promise.resolve(1)
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)

const pAll = Promise.all([p1, p2, p3])
const pAll2 = Promise.all([p1, p2])
//因为其中p3是失败所以pAll失败
pAll.then(
value => {
   console.log('all onResolved()', value)
 },
reason => {
   console.log('all onRejected()', reason) 
 }
)
// all onRejected() 3
pAll2.then(
values => {
   console.log('all onResolved()', values)
 },
reason => {
   console.log('all onRejected()', reason) 
 }
)
// all onResolved() [1, 2]

image.png

  • 当有一个失败,.then返回的是失败的值,
  • 当所有promise成功,.then 返回的值,是数组,数组里面所有成功的值

Promise.race方法

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

all是等所有的异步操作都执行完了再执行then方法,那么race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了race的成功回调还是失败回调,其余的将不会再进入race的任何回调

function promiseClick1(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('2s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('2s数字太于10了即将执行失败回调');
				}
			}, 2000);
		   })
		   return p
	   }
	   function promiseClick2(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('3s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('3s数字太于10了即将执行失败回调');
				}
			}, 3000);
		   })
		   return p
	   }
	   function promiseClick3(){
		let p = new Promise(function(resolve, reject){
			setTimeout(function(){
				var num = Math.ceil(Math.random()*20); //生成1-10的随机数
				console.log('4s随机数生成的值:',num)
				if(num<=10){
					resolve(num);
				}
				else{
					reject('4s数字太于10了即将执行失败回调');
				}
			}, 4000);
		   })
		   return p
	   }
 
	Promise
		.race([promiseClick3(), promiseClick2(), promiseClick1()])
		.then(function(results){
			console.log('成功',results);
		},function(reason){
			console.log('失败',reason);
		});


当2s后promiseClick1执行完成后就已经进入到了then里面回调,在then里面的回调开始执行时,promiseClick2()和promiseClick3()并没有停止,仍旧再执行。于是再过3秒后,输出了他们各自的值,但是将不会再进入race的任何回调。**如图2s生成10进入race的成功回调后,其余函数继续执行,但是将不会再进入race的任何回调,2s生成16进入了race的失败回调,其余的继续执行,但是将不会再进入race的任何回调。

image.png

race的使用比如可以使用在一个请求在10s内请求成功的话就走then方法,如果10s内没有请求成功的话进入reject回调执行另一个操作。

 //请求某个table数据
    function requestTableList(){
        var p = new Promise((resolve, reject) => {
               //去后台请求数据,这里可以是ajax,可以是axios,可以是fetch 
                resolve(res);
        });
        return p;
    }
  //延时函数,用于给请求计时 10s
      function timeout(){
          var p = new Promise((resolve, reject) => {
              setTimeout(() => {
                  reject('请求超时');
              }, 10000);
          });
          return p;
      }
      Promise.race([requestTableList(), timeout()]).then((data) =>{
        //进行成功回调处理
        console.log(data);
      }).catch((err) => {
        // 失败回调处理
          console.log(err);
      });
请求一个接口数据,10s内请求完成就展示数据,10s内没有请求完成就提示请求失败

这里定义了两个promise,一个去请求数据,一个记时10s,把两个promise丢进race里面赛跑去,如果请求数据先跑完就直接进入.then成功回调,将请求回来的数据进行展示;如果计时先跑完,也就是10s了数据请求还没有成功,就先进入race的失败回调,就提示用户数据请求失败进入.catch回调,(ps:或者进入reject的失败回调,当.then里面没有写reject回调的时候失败回调会直接进入.catch)

几个关键的问题

1

const p =new Promise(( resolve,reject)=>{
            throw  new Error('出错了')

            // 这里的代码出错误了或主动抛出异常,promise变为rejected失败状态,
        })

        p.then(
            value=>{},
            reason=>{console.log('reason',reason);}
        )

        p.then(
            value=>{},
            reason=>{console.log('reason',reason);}
        )

        // 多次调用,都会执行回调函数

2

image.png

image.png

image.png

3

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

4

image.png

 new Promise((resolve,reject)=>{
            setTimeout(()=>{
                resolve(1)
                console.log('异步任务1');
            },1000)
         }).then(
            value=>{
                console.log('任务1的结果',value);
                console.log('同步任务2');
                return 2
             
              
            },
         ).then((value)=>{
             console.log( '任务2的结果',value);
             return new Promise((resolve,reject)=>{
                setTimeout(()=>{
                    resolve(3)
                    console.log('异步任务3');
                },1000)
             })
         }).then(
            value=>{console.log('任务3的结果',value);}
         )

image.png

5

image.png

image.png

  new Promise((resolve,reject)=>{
            reject(1)
        }).then(
            value=>{
                console.log( 'onResolved1()',value);
                return 2
            }
        ).then(
            value=>{
                console.log('onResolved2()',value);
                return 3
            }
        ).catch(
            reason=>{
                console.log('onRjected()',reason);  //onRjected() 1
                return new Promise(()=>{})  
                // 返回一个pedding状态的Promise,中断,后面的then没有输出
            }
        ).then(
            value=>{ console.log( 'onResolve3',value);},
            reason=>{ console.log( 'onReject3',reason);},
        )

async与await

image.png

async function f() {
  return 'hello world';
}

f().then(v => console.log(v))
// "hello world"

上面代码中,函数`f`内部`return`命令返回的值,会被`then`方法回调函数接收到。

async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。法回调函数接收到。抛出的错误对象会被catch方法回调函数接收到

async function f() {
  throw new Error('出错了');
}

f().then(
  v => console.log('resolve', v),
  e => console.log('reject', e)
)
//reject Error: 出错了

image.png

promise对象的状态变化 async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

async function getTitle(url) {
  let response = await fetch(url);
  let html = await response.text();
  return html.match(/<title>([\s\S]+)</title>/i)[1];
}
getTitle('https://tc39.github.io/ecma262/').then(console.log)
// "ECMAScript 2017 Language Specification"

上面代码中,函数getTitle内部有三个操作:抓取网页、取出文本、匹配页面标题。只有这三个操作全部完成,才会执行then方法里面的console.log

await

image.png

任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}
上面代码中,第二个`await`语句是不会执行的,因为第一个`await`语句状态变成了`reject`

错误处理 如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

async function f() {
  await new Promise(function (resolve, reject) {
    throw new Error('出错了');
  });
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错
上面代码中,`async`函数`f`执行后,`await`后面的 Promise 对象会抛出一个错误对象,导致`catch`方法的回调函数被调用,它的参数就是抛出的错误对象。

防止出错的方法,也是将其放在try...catch代码块之中。

async function f() {
  try {
    await new Promise(function (resolve, reject) {
      throw new Error('出错了');
    });
  } catch(e) {
     console.log(e);
  }
  return await('hello world');  //当上面的代码出错误,执行这个代码
}

f().then()


如果有多个await命令,可以统一放在try...catch结构中。

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

注意点 第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try...catch代码块中。

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

// 另一种写法

async function myFunction() {
  await somethingThatReturnsAPromise()
  .catch(function (err) {
    console.log(err);
  });
}

第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发

let foo = await getFoo();
let bar = await getBar();

上面代码中,getFoogetBar是两个独立的异步操作(即互不依赖),被写成继发关系。这样比较耗时,因为只有getFoo完成以后,才会执行getBar,完全可以让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
上面两种写法,`getFoo`和`getBar`都是同时触发,这样就会缩短程序的执行时间。

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

async function dbFuc(db) {
  let docs = [{}, {}, {}];

  // 报错
  docs.forEach(function (doc) {
    await db.post(doc);
  });
}

js异步操作之宏队列与微队列

image.png

image.png

image.png

image.png

image.png

image.png

面试题目

image.png

image.png