javascript中Promise使用详解

前言:

做过前端开发的都知道,JavaScript是单线程语言,浏览器只分配给JS一个主线程,用来执行任务,但是每次一次只能执行一个任务,这些任务形成一个任务队列排队等候执行;但是某些任务是比较耗时的,如网络请求,事件的监听,以及定时器,如果让这些非常耗时的任务一一排队等候执行,那么程序执行效率会非常的低,甚至会导致页面假死。因此,浏览器为这些耗时的任务开辟了新的线程,主要包括http请求线程、浏览器事件触发线程、浏览器定时触发器,但是这些任务都是异步的,这就涉及到了前端开发的异步回调操作处理,前端处理异步回调操作用到的就是Async/Await和Promise。

而且在前端相关的面试的时候,面试官一般都会问到关于Promise相关的使用问题,甚至在笔试中也会出一些关于Promise和setTimeout的执行结果,这说明Promise的使用对于前端开发来说是非常重要的一个知识点。那么本篇博文就来分享一下关于Promise的使用相关的知识点。

 

一、首先,要知道为什么要用Promise语法?

在介绍Promise之前,首先来了解一下JavaScript的特性。搞前端开发的都知道JS是一个传统的单线程编程,它里面的程序运行都是同步的,只有一个主线程,但是随着技术的发展,为了解决前期的缺陷,引入了异步思想,也就是一个异步过程的执行将不再与原有的序列有顺序关系,这就解决了同步执行引起的执行效率不高的缺陷。用一句话解释:异步就是从主线程发射一个子线程来完成任务。

再来了解一下Promise,Promise是ES6新增加的,它是一个由ES6提供的类,其主要目的就是很好的处理复杂的异步任务,但是它不是任何浏览器都能支持,比如一些旧版本的浏览器就不支持,只有苹果的Safari10和Windows的Edge14版本以上浏览器才开始支持ES6特性的。

Promise作为替代回调函数执行,作为异步操作的处理方法;是JS异步执行时候,回调函数嵌套回调函数的这一问题的解决方法,Promise更简洁地控制函数执行流程。Promise对象其实表示是一个异步操作的最终成败,以及结果值,也就是一个代理值,是ES6中的一种异步回调解决方案。

Promise对象代理的值其实是未知的,状态是动态可变的,因此Promise对象的状态有三种:进行中、结束、失败,它运行的时候,只能从进行中到失败,或者是从进行中到成功。使用Promise对象只要是通过同步的表达形式来运行异步代码。

  • pending:初始状态,既不成功,也不失败;
  • fulfilled:操作成功结束;
  • rejected:操作失败。
Promise属于Es 新增的内置构造函数,可以直接调用。

英文意思是:承诺

有三种状态:pending-等待态 resolved-成功态 rejected-失败态

new的时候传入一个执行(器函数)

=〉1 这个执行器会立即执行

=〉2 这个执行器接受两个函数参数 分别是resolve和rejected

=〉3 调用resolve,会把promise状态从pending—>resolved

​ 调用reject,会把promise状态从pending—>rejected

怎么构造Promise?这里简单举一个构造Promise的示例:

new Promise(function (resolve, reject) {   // 要做的事情...});

 

通过上面新构造一个Promise 对象好像并没有看出它是怎样很好的处理复杂的异步任务的,那么接下来就是Promise的核心操作。

 

二、接着,来了解一下回调地狱(Callback Hell)

回调地狱也叫回调嵌套或者函数混乱的调用,通俗点讲就是:需要发送三个网络请求,第三个请求依赖第二个请求的结果,第二个请求依赖第一个请求的结果。不断增加的嵌套使用。

回调函数的弊病:

开发者阅读起来很费神、吃力,不利于排查错误,更不能直接return,等等。

setTimeout(() => {
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
},3000)
},2000)
},1000)
 

 

三、最后,也是本章的重头戏,Promise的基本使用

Promise 构造函数只有一个参数,是一个函数,这个函数在构造之后会直接被异步运行,所以我们称之为起始函数。起始函数,也就是Promise的构造函数里面有两个参数:resolve和reject,该两个参数表示的是异步操作的结果,也就是Promise成功或失败的状态。

当 Promise 被构造时,起始函数会被异步执行;resolve 和 reject 都是函数,其中调用 resolve 代表一切正常,reject 是出现异常时所调用的。

  • ①异步操作成功,调用resolve函数,将Promise对象的状态改为fulfilled。
  • ②异步操作失败,调用rejected函数,将Promise对象的状态改为rejected。

Promise基本用法:

创建:

const  p = new Promise((resolve, reject) => {
	console.log(123);
  resolve('我成功了');
  // reject('我失败了');
})

then方法使用:

// promise实例的then方法(then方法是异步的),当p的状态是成功的时候,第一个回调函数会被执行,当p的状态是失败的时候, 第二个回调函数会被执行
p.then(res => {
  console.log('SUCCESS',res);
},error => {
  console.log('ERROR',error);
})
console.log(456);
//最终输出结果如下: 123     456    SUCCESS

then的链式调用 p.then().then()....

// 第一个then到底执行哪个回调,取决于p的状态
// 后面的then到底执行哪个回调 取决于上一次then执行的回调函数的返回值
// => 假如上一次then返回的是普通纸,数字、字符串、对象。。。 那么本次then执行成功回调
// => 假如上一次then返回的是Promise对象,假如该Promise对象是成功态,本次then执行成功回调,失败态,本次then执行失败回调
// => 上一次then回调函数执行过程中出错,直接走下一次then的失败回调。
p.then((r) => {
  console.log('aaa',r);
  return 1; // 不写return默认返回undefined  return结果会作为下一个then成功回调参数。
  // reject(); 则走下一个then的失败回调
}).then((r) => {
  console.log('bbb',r)
})

catch方法使用:

// 失败态的时候 会执行catch传入的回调函数
p.catch(e => {
  console.log('Fail',e);
});

all方法使用:

// 异步并发时,可以用all方法,并且返回结果是一个数组,数组结果的顺序则是请求的先后顺序,不会改变。
// 并且每个请求都成功才会返回成功结果数组,否则会报错
const p1 = new Promise((resolve,reject) => {
  // 异步
  setTimeout( ()=> {
    resolve('success1');
  },5000);
});
const p2 = new Promise((resolve,reject) => {
  // 异步
  setTimeout( ()=> {
    resolve('success2');
  },1000);
})
const p3 = new Promise((resolve,reject) => {
  // 异步 xi
  setTimeout( ()=> {
    resolve('success3');
  },1000);
})
Promise.all([p1,p2,p3]).then((res) => {
  console.log(res); // ['success1','success2','success3']
})

async 和 await (Es7语法,Es6中已有提案)

异步流程 回调 —> promise —> generator —> async+await

// async是函数修饰关键字
// await 必须在async修饰的函数内部使用
// await 后面是一个promise对象才有意义
async function fn() {
  const res = await this.getList(); // this.getList()获取列表数据的请求函数
  console.log(res);
}

js实例:

//小明他老爸说:假设这次考试及格的话,给他买AJ,不及格,吊起来来打
//$.ajax({
//  url: "去考试",
//  success: function(res){
//      if(res >= 60){
//          console.log("给他买aj");
//      }else{
//          console.log("吊起来来打");
//      }
//  }
//});

 // 小红明他老爸说:假设这次考试及格的话,给她买5年模拟3年考试,不及格,带它去看鲨鱼
 // $.ajax({
 //     url: "去考试",
 //     success: function(res){
 //         if(res >= 60){
 //             console.log("给她买5年模拟3年考试");
 //         }else{
 //             console.log("带它去看鲨鱼");
 //         }
 //     }
 // });
 

 function test(resolve, reject){
     $.ajax({
         url: "去考试",
         success: function(res){
             if(res >= 60){
                 resolve();
             }else{
                 reject();
             }
         }
     });
 }

 // test(function(){
 //     console.log("给他买aj");
 // }, function(){
 //     console.log("吊起来来打");
 // })

 // Promise
 var promise = new Promise(function(resolve, reject){
     setTimeout(function(){
         // 模拟语文分数
 var res = 61;

 if(res >= 60){
     resolve("小明同学语文及格");
         }else{
             reject();
         }
     }, 2000)
 })

 var promise2 = new Promise(function(resolve, reject){
     setTimeout(function(){
         // 模拟数学分数
         var res = 61;

         if(res >= 60){
             resolve();
         }else{
             reject();
         }
     }, 2000)
 })

 // 考语文
 // promise.then(function(){
 //     console.log("给他买aj");

 //     // 先考完语文,再考数学
 //     promise2.then(function(){
 //         console.log("再买一双aj");
 //     });

 // }).catch(function(){
 //     console.log("吊起来来打");
 // })

 //使用async修饰函数,且使用await 修饰promise,用来替代promise.then().then()多次执行的问题,让代码更加简洁
 async function test(){
     // 先考语文
    var res = await promise;
    console.log(res)

    // 再考数学
    var res2 = await promise2;
    console.log(res2);

    // ...其他科目考试
 }

 test();

又例如:

通过一个异步读取文件的小栗子来对比下Promise和 async 的异同点

const fs = require('fs')

function readFile(fileName) {
  return new Promise((resolve, reject) => {
    fs.readFile(fileName, (err, data) => {
      if(err) {
        reject(err)
      }
      resolve(data.toString())
    })
  })
}
(1)、通过 Promise 读取文件
 
readFile('data/a.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/b.txt').then(res => console.log(res))
                      .catch(err => console.log(err))

readFile('data/c.txt').then(res => console.log(res))
                      .catch(err => console.log(err))
(2)、通过 async 函数读取文件
充分吸取了 Promise 优点,同时避免了缺点
async function read() {
  let readA = await readFile('data/a.txt')
  let readB = await readFile('data/b.txt')
  let readC = await readFile('data/c.txt')

  console.log(readA)
  console.log(readB)
  console.log(readC)
}

read()

最终的输出结果

async 函数的多种使用形式

// 函数声明
async function foo() {
  // ....
}

// 函数表达式
let foo = async function() {
  // ....
}

// 箭头函数
let foo = async() => {}

// 对象的方法
let obj = {
  name: 'Roger',
  async foo() {

  }
}
obj.foo().then(res => {
  // ....
})

// 类的方法
class Student{
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  async say() {
    return `My name is ${this.name}, I'm ${this.age} years old !`
  }
}

let jim = new Student('Jim Green', 13)
jim.say().then(res => console.log(res))   // My name is Jim Green, I'm 13 years old !
async 基本用法
 
async 函数返回一个 Promise 实例对象,可以使用 then 方法添加回调函数。
 
当函数执行时,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
// 休眠 ms 毫秒
function sleep(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms)
  })
}

async function print(ms) {
  console.log('start... ...')
  await sleep(ms)
  console.log('end... ...')
}

print(1000)

(1)、async 函数内部 return语句返回的值,会成为then方法回调函数的参数

async function foo() {
  return 'hello world'
}

foo().then(res => console.log(res))   // hello world
(2)、async 函数内部抛出错误,会导致返回的 Promise对象变成reject状态,抛出的错误会被catch方法回调函数接收到
 
async function bar() {
  return new Error('Error... ...')
}

bar().then(res => console.log(res))
     .catch(err => console.log(err))   // Error: Error... ...
(3)、只有 async 函数内部的异步操作执行完,才会执行 then方法指定的回调函数
 
async function baz() {
  await new Promise(resolve => {
    console.log('执行第一个异步操作')
    setTimeout(resolve, 2000)
  })

  await new Promise(resolve => {
    console.log('执行第二个异步操作')
    setTimeout(resolve, 3000)
  })

  return '异步执行完毕再执行then方法'
}

baz().then(res => {console.log(res)})

4、await 命令
await 用于等待一个 Promise对象,它只能在一个 async函数中使用
[return_value] = await expression

表达式:一个 Promise对象或者任何要等待的值

返回值:返回 Promise对象的处理结果。如果等待的不是 Promise对象,则返回该值本身

await命令会暂停当前 async函数的执行,等待 Promise处理完成。如果 Promise正常处理,其回调的 resolve函数参数会作为 await表达式的返回值,继续执行 async函数。如果 Promise处理异常,await表达式会把 Promise的异常原因抛出

// 如果 await 命令后的表达式的值不是一个 Promise,则返回该值本身
async function foo() {
  return await 123
}

foo().then(res => console.log(res))    // 123


// 如果 Promise 正常处理(fulfilled),其回调的resolve函数参数作为 await 表达式的返回值
async function bar() {
  let f = await new Promise((resolve, reject) => {
    resolve('我是表达式的返回值')
  })
  console.log(f)   // 我是表达式的返回值

  return 'ending'
}

bar().then(res => {console.log(res)})    // ending


// 如果 Promise 处理异常(rejected),await 表达式会把 Promise 的异常原因抛出
async function baz() {
  await new Promise((resolve, reject) => {
    reject(new Error('出错啦......'))
  })
}

baz().then(res => console.log(res))
     .catch(err => console.log(err))     // Error: 出错啦......
(1)、任何一个 await语句后面的 Promise对象变为 reject状态,那么整个 async函数都会中断执行
async function foo() {
  await Promise.reject('error')
  return 'ending'   // 未执行
}

foo().then(res => console.log(res))
// Uncaught (in promise) error
(2)、如果希望当前面的异步操作失败时,不要中断后面的异步操作,可以把前面的 await放在try...catch结构里面
async function foo() {
  try{
    await Promise.reject('error')
  } catch(e) {
    
  }
  return await Promise.resolve('执行完毕')
}

foo().then(res => console.log(res))    // 执行完毕
还可以在 await后面的 Promise对象再跟一个 catch方法,处理前面可能出现的错误
async function foo() {
  await Promise.reject('error').catch(err => console.log(err))
  return '执行完毕'
}

foo().then(res => console.log(res))    // 执行完毕
(3)、如果想让多个异步操作同时触发,缩短程序的执行时间,可以参考如下两种写法
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章