浅谈对Promise的理解以及在工作中的应用

浅谈对Promise的理解以及在工作中的应用

  • Promise的概念
    • 背景知识
    • JavaScript的同步和异步
    • JavaScript事件循环
    • 回调函数进行异步操作
    • 解决方案:Promise
  • Promise 在工作中的运用
    • 创建Promise
    • Promise封装AJAX
    • Promise链式操作
    • Promise.all()
    • Promise.race()
    • async和await
  • 总结

Promise的概念

在开始讲解Promise前,我们先大致了解一下js的运行机制以及多个任务是怎么运作的。

背景知识

众所周知,JavaScript是一门单线程语言,也就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着,这样会造成浏览器处于假死状态,严重影响用户体验。为了解决这个问题,js引入了异步的概念,在执行任务时挂起处于等待中的任务,先运行排在后面的任务。等到刚才挂起的任务返回了结果,再回过头,把挂起的任务继续执行下去。于是,js将所有的任务分成了两种,一种是同步任务synchronous),另一种是异步任务asynchronous)。

JavaScript的同步和异步

同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行;
除了广义的同步任务和异步任务,对任务更精细的定义:

宏任务(macro-task):包括整体代码script,setTimeout,setInterval
微任务(micro-task):Promise,process.nextTick

宏任务中有微任务,一定要将宏任务中的微任务执行完毕,再去执行下一个宏任务
那究竟什么是任务队列呢,同步任务和异步任务又是怎么执行的,这就要引入js的运行机制:事件循环(event loop)

JavaScript事件循环

浅谈对Promise的理解以及在工作中的应用_第1张图片
js事件循环运作机制如下:

  1. 同步任务和异步任务分别进入不同的“场所”,同步任务进入主线程,异步任务进入Event Table (事件表)并注册函数
  2. 指定的事件完成后,Event Table (事件表)会将这个函数移入到Event Queue (事件队列)
  3. 当主线程的任务完成后,会检查Event Queue (事件队列),如果有任务就全部执行,如果没有就进入下一个宏任务
  4. 这个过程会不断的重复,这就叫事件循环

回调函数进行异步操作

和同步操作不同,异步操作是不会立即返回结果的(如发起网络请求,下载文件,操作数据库等)。如果我们后续的函数需要之前返回的结果,又怎样使之前的异步操作在其完成时通知到后续函数来执行呢?

通常,我们可以将这个函数先定义,存储在内存中,将其当做参数传入之前的异步操作函数中,等异步操作结束,就会调用执行这个函数,这个函数就叫做回调函数(callback)

// 下载
function download(callback){
    // 模拟异步操作
    setTimeout(function(){
        // 调用回调函数
        callback('下载完成');
    }, 1000);
}

function callback(value){
    // 下载完成的处理
    console.log(value);
}

download(callback);

// 这段代码将在1秒后在控制台打印“下载完成”

但假如callback函数同样是个异步函数,且callback里又嵌入了callback呢? 例如需求是等待第一个文件下载完成后,再下载第二个文件,等待第二个文件下载完成后,再下载第三个文件…,这样的话,上面这种方法就不可取了,因为会产生很多的函数嵌套,嵌套太深容易引发回调地狱(指的是回调函数里嵌套回调函数,使得代码可读性非常差,容易陷于无止尽的循环)

     //回调地狱
    setTimeout(function () {  //第一层
        console.log('张三');//等3秒打印张三在执行下一个回调函数
        setTimeout(function () {  //第二层
            console.log('李四');//等2秒打印李四在执行下一个回调函数
            setTimeout(function () {   //第三层
                console.log('王五');//等一秒打印王五
            }, 1000)
        }, 2000)
    }, 3000)

解决方案:Promise

所以为了回调地狱的问题,promise方案应运而生,它是对回调方法的一种封装,是用来处理异步操作的,可以让我们写异步调用的时候写起来更加优雅,更加美观。
以下是promise的一些特点

  1. Promise 对象代表一个异步操作,对象的状态不受外界影响,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)、settled (结束)只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,且一旦状态改变就不可再变
  2. Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据,其中then方法的两个参数是resolve(成功回调),reject(失败回调)。异步任务执行成功时调用resolve函数返回结果,反之调用reject,根据不同的任务,由开发者来决定resolve和reject在函数体内的位置
  3. then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then/catch方法,链式调用可以保证代码的执行顺序
    promise解决刚才的地狱回调问题:
// promise解决方式
function fn(str) {
    var promise = new Promise(function (resolve, reject) { //resolve是成功的方法  reject是失败的方法  
        //处理异步任务
        var flag = true;
        setTimeout(function () {
            if (flag) {
                resolve(str)
            }
            else {
                reject('失败')
            }
        })
    })
    return promise;
}
 
fn('张三')
    .then((res) => { //then是成功执行的方法 返回的还是一个promise对象
        console.log(res);//打印张三  res是结果
        return fn('李四');
    })
    .then((res) => {
        console.log(res);
        return fn('王五')
    })
    .then((res) => {
        console.log(res);
    })
    .catch((res) => { //catch是失败执行的方法
        console.log(res);
    })

Promise 在工作中的运用

创建Promise

promise构造器只接收一个参数,该参数被称为执行器(executor)的函数。该函数会被传递两个参数(方法),一个叫做resolve,另一个叫做reject。

resolve函数在成功时调用,reject函数在失败时被调用。并且resolve和reject只能被使用一次,如果之后还有resolve和reject也不会被执行了,有点儿类似于return,但是不同点在于,其他代码还会被照常执行。

new Promise((resolve, reject)=> {
  resolve('我是第一次调用resolve');
  console.log('我是其他代码');
  resolve('我是第二次调用resolve'); // 不在起作用
  reject('我来调用reject'); // 不在起作用
})

也可以直接使用Promise.resolve或者Promise.reject来创建成功或者失败的Promise

let p1 = Promise.resolve('我是成功的Promise'),
    p2 = Promise.reject('我是失败的Promise');

Promise封装AJAX

var ajax = function(url) {
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open('get', url, true);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readystate == 4 && xhr.status == 200) {
                resolve(xhr.responseText)
            } else if (xhr.readystate == 4 && xhr.status !== 200) {
                reject(xhr.statusText)
            }
        }
    })
}
ajax.then(console.log(xhr.responseText)); //打印出返回的数据

Promise链式操作

由于Promise的then 方法始终返回一个 Promise 对象, 所以Promise 可以一直调用 then 方法,从而实现链式调用(解决地狱回调)。不管 new Promise 创建出来的执行状态是成功 / 失败,只要在 then / catch方法中通过 return 返回一个结果,不管这个值是 Promise 对象还是普通值,都可以通过链式调用的 .then 方法中获取到这个值,因为 promise.then 方法会默认在返回值的外层包裹一层 Promise 对象,这样才可以实现 Promise 一直通过 .then 的方式去链式调用

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
    setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
    setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
    setTimeout(()=> {
        resolve(name);
    }, 3000)
})

//方式一:链式操作返回promise对象
p1.then((res) => {
    console.log(res+'第一个出场');
    return p2
}).then((res) => {
    console.log(res+'第二个出场');
    return p3
}).then((res) => {
    console.log(res+'第三个出场');
})

//链式操作返回promise对象输出结果
张三第一个出场
李四第二个出场
王五第三个出场


//方式二:链式操作返回普通值
p1.then((res) => {
    console.log('我是'+res);
    return res
}).then((res) => {
    console.log('我是'+res+'的儿子');
    return res
}).then((res) => {
    console.log('我是'+res+'的孙子');
})

//链式操作返回普通值输出结果
张三第一个出场
李四第二个出场
王五第三个出场

Promise.all()

这个方法返回一个新的promise对象,该promise对象在参数对象promises里所有的promise对象都成功的时候才会触发成功,一旦有任何一个promises里面的promise对象失败则立即触发该promise对象的失败。这个新的promise对象在触发成功状态以后,会把一个包含promises里所有promise返回值的数组作为成功回调的返回值,顺序跟promises的顺序保持一致;如果这个新的promise对象触发了失败状态,它会把iterable里第一个触发失败的promise对象的错误信息作为它的失败错误信息。Promise.all方法常被用于处理多个promise对象的状态集合。
简而言之,all方法会将传入的异步操作并行执行,等到它们都执行完后才会进到then方法,从时间上来看取决于最后一个异步任务执行完成的时间

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
        setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
        setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
        setTimeout(()=> {
        resolve(name);
    }, 3000)
})

// promise.all用法
Promise.all([p1,p2,p3]).then((res) => {
    console.log(res+'都已到达终点');
})

// promise.all输出结果
张三,李四,王五都已到达终点

Promise.race()

race就是赛跑的意思,谁先出结果就由谁决定,采用第一个 promise 的值作为它的值,当promises参数里的任意一个子promise被成功或失败后,父promise马上也会用子promise的成功返回值或失败详情作为参数调用父promise绑定的相应句柄,并返回该promise对象。

let p1 = new Promise((resolve, reject)=> {
    let name = '张三'
        setTimeout(()=> {
        resolve(name);
    }, 2000)
})

let p2 = new Promise((resolve, reject)=> {
    let name = '李四'
        setTimeout(()=> {
        resolve(name);
    }, 1000)
})

let p3 = new Promise((resolve, reject)=> {
    let name = '王五'
        setTimeout(()=> {
        resolve(name);
    }, 3000)
})

// promise.race用法
Promise.race([p1,p2,p3]).then((res) => {
    console.log(res+'第一个到达终点');
})

// promise.all输出结果
李四第一个到达终点

async和await

async 是“异步”的简写,而 await 可以认为是 async wait(等待) 的简写。
所以应该很好理解 async 用于申明一个 function 是异步的,返回的是一个 Promise 对象.
而 await 用于等待一个异步方法执行完成,后面必须跟一个Promise对象,但是不必写then(),直接就可以得到返回值


//基本用法的async函数
let asyncFun = async function(){
    return 1
}
console.log(asyncFun())
//会返回一个promise对象

//使用场景
//摇色子方法
function dice(){
    return new Promise((resolve,reject)=>{
        let sino = parseInt(Math.random()*6+1)  //生成一个1~6之间的随机小数
        setTimeout(()=>{
            resolve(sino)
        },2000)
    })
}
//异步方法
 async function text(){
     let n= await dice()
      //await 关键字后面调用摇色子方法执行完毕之后,才进行变量赋值
     console.log("摇出来"+n)  //最后打印出摇出来的数
 }
text()

//输出结果
Promise { 1 }
摇出来5

总结

以上就是我个人关于Promise的理解以及在工作中的应用,总的来说Promise在日常开发工作中的使用还是比较多的,他最大的用途在于让多个异步的任务按照我们想要的方式去执行。但想要彻底理解promise的运行方式及原理,还需要了解js引擎的运行逻辑,任务队列、宏任务微任务等。上文中如果有错误的地方欢迎指正,大家共同进步,越秃越强!

你可能感兴趣的:(javascript,前端,html)