关于 Promise 的一些简单理解

一、ES6 中的 Promise

1、JS 如何解决 异步问题?

(1)什么是 同步、异步?
同步指的是 需要等待 前一个处理 完成,才会进行 下一个处理。
异步指的是 不需要等待 前一个处理 完成,就可以进行下一个处理。

(2)JS 是单线程 还是 多线程的?
JS 是单线程的,也即执行处理时 采用 同步机制。而 JS 实现异步 是借助 浏览器的 多线程机制 完成的。
JS 作为浏览器的脚本语言,其根本目的是 实现用户 与 浏览器进行交互,假如现在用户 需要删除一个节点 A,但同时又向节点 A 中添加节点 时,若 JS 为多线程,则一个线程用于删除,一个线程用于添加,那此时浏览器应该以哪个线程为准,这就涉及到了复杂的同步问题。而 JS 若为单线程,则按实际顺序执行即可,不必担心线程之间的冲突。

(3)JS 如何解决同步、异步问题?
JS 是单线程的,也即意味着 下一个任务 需要等待 上一个任务完成后才会去处理,如果上一个任务执行时间非常长,那么将会陷入长时间等待,此方式肯定不可取。
那么可以将 需要长时间处理 或者 不需要立即处理 的任务 抽取出来,等待其 处理完成后 再去执行,从而使 JS 可以继续处理下一个任务。也即 异步处理。
JS 是借助 浏览器的多线程机制 去实现异步处理。

【实现异步大致流程:】
Step1:将 JS 执行过程视为 主线程,也即同步执行 JS 中的代码。
Step2:主线程执行过程中,若发现了异步任务,则将其交给浏览器(浏览器创建多个线程),继续进行下一步处理。
    且维护一个 异步任务结果队列(回调函数队列),异步任务完成后,向 异步任务结果队列 中 放置一个事件(即 回调函数)。
Step3:主线程执行完 所有的同步代码后,开始监听 回调函数 队列,若发现 某个回调函数 状态已完成,则执行该回调函数。

2、什么是 Promise?

(1)简单理解?
Promise 是异步编程的一种解决方案。可以理解为一个容器,里面保存着未来才会结束的某个操作(异步操作)的结果,通过 Promise 对象可以获取异步操作的结果。

(2)直观理解一下?
直接使用 console.dir(Promise) 控制台打印一下 Promise 对象的属性。
如下图所示,Promise 为一个构造函数,通过 new Promise() 构建出来的对象可有相应的 catch、then 等方法。

关于 Promise 的一些简单理解

(3)Promise 的特点
特点一:对象的状态不受外界影响。
Promise 有三种状态,Pending (进行中)、Resolved (解决)、Rejected (失败)。
只有异步操作的结果能决定 Promise 处于哪种状态,其余操作无法改变该状态,无法中途取消操作。

特点二:状态改变后,不再变化。
状态改变的情况,Pending -> Resolved 、 Pending -> Rejected。
一旦状态改变,其不会再改变。

(4)如何使用?
需要使用 new 去实例化一个 Promise 对象,参数为 一个函数。
函数的参数为 resolve、reject ,分别表示两个回调函数,由 JavaScript 引擎提供。
resolve 回调函数是改变状态, Pending -> Resolved ,即异步操作成功后调用,并将异步操作的成功结果作为参数向外传递。
reject 回调函数也是改变状态, Pending -> Rejected,即异步操作失败后调用,并将异步操作的失败结果作为参数向外传递。
使用 then 方法可以处理 resolve、reject 传递出来的结果。其接受两个回调函数作为参数。第一个回调函数用来处理 resolve 传递的结果,第二个回调函数用来处理 reject 传递的结果。第二个回调函数可选。
一般情况下,可使用 catch 方法处理 reject 传递出来的结果。其作用等价于 then 方法中的第二个回调函数。

注:
new promise() 的过程仍属于同步(同步触发 异步操作),
而进行 then()、catch() 的过程才真正意义上属于 异步(也即回调状态变化后触发)。

【格式一:(直接通过对象操作)】
var promise = new Promise((resolve, reject) => {
    if(异步操作成功) {
        resolve(data);
    } else {
        reject(error);
    }
});

promise.then((data) => {
    // 成功的操作
}).catch((error) => {
    // 失败的操作
});

【格式二:(封装成方法进行操作)】
function promise() {
    return new Promise((resolve, reject) => {
        if(异步操作成功) {
            resolve(data);
        } else {
            reject(error);
        }
    });
}

promise().then((data) => {
    // 成功的操作
}).catch((error) => {
    // 失败的操作
});

(5)比较一下 Promise 与 回调函数的区别
new Promise() 返回一个 promise 对象,通过其 then() 方法处理 异步成功的 后续操作,也即相当于异步成功后 进行 回调函数处理。
那直接传 回调函数 并处理不也一样吗?如下代码功能相同。

【Promise 写法:】
function promise() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            resolve('随便什么数据');
        }, 2000);
    });
}

promise().then((data) => {
    console.log(data);
});

【callback 写法:(功能等同于上一种写法)】
function promise2(callback) {
    //做一些异步操作
    setTimeout(callback, 2000);
}

promise2(function() {
    console.log('回调函数')
});

那为什么还要引入 promise 呢?promise 另一个特点就是可以 链式调用,当出现 多层回调时,可以简化代码的处理(比 callback 写法更简单、灵活)。
callback 多层回调,一层套一层,不易维护。而 promise 可以随时切换 下一个回调的逻辑(通过 then 指定下一个回调处理),从而简化代码的处理。

【Promise 写法:(then 方法可以向后接着传递 promise 对象 或者 直接传递值,实现多层回调)】
function promise() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第一次回调');
            resolve('随便什么数据');
        }, 2000);
    });
}

function promise2() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第二次回调');
            resolve('随便什么数据');
        }, 2000);
    });
}

function promise3() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第三次回调');
            resolve('随便什么数据');
        }, 2000);
    });
}

promise().then((data) => {
    console.log(data);
    return promise2();
}).then((data) => {
    console.log(data);
    return promise3();
}).then((data) => {
    console.log(data);
    return '第四次回调';
}).then((data) => {
    console.log(data);
});

【callback 写法:(功能等同于上一种写法,但是一层套一层,容易出错,维护起来也麻烦)】
function promise4(callback) {
    //做一些异步操作
    setTimeout(callback, 2000);
}

function callback() {
    setTimeout(function() {
        console.log("第一次回调");
        callback2();
    }, 2000);
}

function callback2() {
    setTimeout(function() {
        console.log("第二次回调");
        callback3();
    }, 2000);
}

function callback3() {
    setTimeout(function() {
        console.log("第三次回调");
    }, 2000);
}

promise4(callback());

(6)promise 关于 all() 的使用
all() 提供了 同步执行异步操作的能力,其接收 promise 数组作为参数,表示当 所有的异步 pormise 均处理完成后 才会 继续执行。
所有异步操作会 同时处理,全部成功后,会将所有异步返回的数据 封装成数组并 传递给 then(),若有一个异步处理失败,则其会进入 catch()。
注:
all() 最终完成时间 以 最慢的异步操作为准,所有异步操作均成功后才会被 then 处理。
all() 提供同步触发 promise 异步操作的过程,若在某个中间 promise 中定义了死循环,那么后面的 js 代码将都会被卡死、不会执行。

使用场景:
适用于需要同时执行多个异步操作的场景。
比如:游戏加载页面,需要异步加载资源文件,等待所有资源加载完毕后,开始执行游戏。

【all() 举例:】
function promise() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第一次回调');
            resolve('第一次回调成功');
        }, 2000);
    });
}

function promise2() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第二次回调');
            resolve('第二次回调成功');
        }, 2000);
    });
}

Promise.all([promise(), promise2()])
.then(function(results) {
    console.log("2 个回调全部执行成功");
    console.log(results);
}).catch(function(errors) {
    console.log("2 个回调中至少一个执行失败");
    console.log(errors);
});

【输出结果:】
第一次回调
第二次回调
2 个回调全部执行成功
["第一次回调成功", "第二次回调成功"]

(7)promise 关于 race() 与 any() 的使用
race() 、any() 也可以同步处理多个 异步操作,但是稍微有些区别。
any() 强调的是 多个异步操作中,第一个成功执行的 异步操作,会进入 then() 中,若全部失败,则进入 catch()。
race() 强调的是 多个异步操作中,第一个执行完成的 异步操作,若第一个执行完成的操作出错,则进入 catch(),否则进入 then()。

使用场景:
race() 可用于给某个 异步请求 设置超时时间。
比如: A 异步操作执行时间为 0~10 秒,B 异步操作执行时间为 5 秒,若 A 操作在 5 秒内完成,则执行 A 的结果,否则执行 B 的结果。

any() 可用于获取加载系统资源。
比如:有多个服务器存放相同的资源,可以使用 any() 通过多个异步操作 分别访问这些服务器,哪个 异步操作 先成功完成,则使用哪个服务器的资源。

【race(): 针对第一个执行完成的异步操作,成功进入 then(),出错进入 catch()】
function promise() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第一次回调');
            reject('第一次回调失败');
        }, 1000);
    });
}

function promise2() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第二次回调');
            resolve('第二次回调成功');
        }, 2000);
    });
}

Promise.race([promise(), promise2()])
.then(function(results) {
    console.log('第一次成功')
    console.log(results);
}).catch(function(errors) {
    console.log('第一次失败')
    console.log(errors);
});

【输出结果:】
第一次回调
第一次失败
第一次回调失败
第二次回调

【any(): 针对第一个执行成功的操作,执行成功进行 then(),全部执行失败则执行 catch()】
function promise() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第一次回调');
            reject('第一次回调失败');
        }, 1000);
    });
}

function promise2() {
    return new Promise((resolve, reject) => {
        //做一些异步操作
        setTimeout(function(){
            console.log('第二次回调');
            resolve('第二次回调成功');
        }, 2000);
    });
}

Promise.any([promise(), promise2()])
.then(function(results) {
    console.log('至少有一个成功')
    console.log(results);
}).catch(function(errors) {
    console.log('全部失败')
    console.log(errors);
});

【输出结果:】
第一次回调
第二次回调
至少有一个成功
第二次回调成功

二、jQuery 中的 Promise

1、$.Deferred()

(1)简单理解
Deferred 是 jQuery 实现 异步编程的一种解决方案,使用起来与 Promise 类似,也可以实现链式调用。通过 Deferred 对象可以获取异步操作的结果。

(2)直观了解一下?
直接使用 console.dir($.Deferred()) 控制台打印一下 Deferred 对象的属性。
如下图所示,$.Deferred() 返回一个对象,称其为 Deferred 对象,可以看到其内部定义了一些方法:resolve()、reject()、done()、fail() 等。通过这些方法接收、传递 回调函数 从而实现 异步调用。

关于 Promise 的一些简单理解

(3)$.Deferred() 的使用?
使用基本上与 Promise 类似,但是语法糖上有些许差别。
比如:Promise 中的 then()、catch() 与 Deferred 中的 then()、done()、fail() 功能相同。

【常用方法:】
(1)jQuery.Deferred(function)  或者  $.Deferred(function)
    创建一个新的 Deferred 对象,function 是可选参数,若存在则在构建 Deferred 对象后执行。

(2)deferred.then(resolve, reject)
    异步操作结束后触发,resolve 表示异步操作成功后触发的回调函数, reject 表示异步操作失败后触发的回调函数。

(3)deferred.done()
    异步操作成功后触发的回调函数,等同于 then() 中的 resolve。

(4)deferred.fail()
    异步操作失败后触发的回调函数,等同于 then() 中的 reject。

(5)deferred.resolve()
    手动将 异步操作状态 变为 解决,会立即触发 done()。

(6)deferred.reject()
    手动将 异步操作状态 变为 失败,会立即触发 fail()。

(7)deferred.always()
    不管异步操作是成功还是失败,均会执行。

(8)deferred.promise()
    在原来 deferred 对象的基础上返回一个受限的 deferred 对象。
    受限指的是 指开放与 状态变化无关的方法(done、fail 等),不开放 与 状态变化有关的方法(resolve、reject)。
    即无法从外部主动改变当前 deferred 对象的状态。

通过上面的方法介绍,可以看到 Deferred 其实用起来与 Promise 很类似。稍稍有些许差别。
比如:Deferred 对象可以直接调用其 resolve、reject 方法直接去改变当前异步操作的状态,当然这样的操作是有风险的,毕竟异步操作有了提前结束的可能。而其调用 promise 方法可以返回一个受限的对象,无法主动结束异步操作,此时的情况就与 Promise 的使用类似了。

【Deferred 用法举例:(调用 promise 方法返回受限的 Deferred 对象)】
function testDeferred() {
    let deferred = $.Deferred(); // 创建一个 Deferred 对象

    setTimeout(function() {
        // 手动改变状态
        deferred.resolve("调用成功");
    }, 2000);

    return deferred.promise();
}

testDeferred().then(data => {
    console.log(data);
});

【Promise 用法举例:(与上面 Deferred 效果是一致的)】
function testPromise() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            // 手动改变状态
            resolve("调用成功");
        }, 2000);
    });
}

testPromise().then(data => {
    console.log(data);
});

【Deferred 用法举例:(Deferred 未受限时,在外部可以直接改变 异步操作状态,提前结束异步操作)】
function testDeferred() {
    let deferred = $.Deferred(); // 创建一个 Deferred 对象

    setTimeout(function() {
        // 手动改变状态
        deferred.resolve("调用成功");
    }, 2000);

    return deferred;
}

let deferred = testDeferred(); // 获取到 Deferred 对象

deferred.then(function(data) {
    // 异步操作成功后执行
    console.log(data);
}, function(error) {
    // 异步操作失败后执行
    console.log(error);
}).always(function() {
    // 异步操作成功或者失败都会执行
    console.log("总是执行")
});

deferred.reject("执行失败"); // 会直接改变 异步操作状态为 失败

2、$.when()、$.ajax()

(1)$.when()
$.when() 功能与 Promise 中的 all() 方法类似,都是同步执行(触发) 多个异步操作,并在所有异步操作执行完成后才会去 调用回调函数。
不过 all() 接收的参数为 promise 数组,then() 接收的参数为 数组形式。
而 $.when() 接收的参数是多个 Deferred 对象,then() 接收的参数 与 Deferred 结果一一对应。

【$.when() 举例:】
function testDeferred() {
    let deferred = $.Deferred(); // 创建一个 Deferred 对象

    setTimeout(function() {
        // 手动改变状态
        deferred.resolve("第一个回调调用成功");
    }, 2000);

    return deferred.promise();
}

function testDeferred2() {
    let deferred = $.Deferred(); // 创建一个 Deferred 对象

    setTimeout(function() {
        // 手动改变状态
        deferred.resolve("第二个回调调用成功");
    }, 3000);

    return deferred.promise();
}

$.when(testDeferred(), testDeferred2()).then(function(data, data2) {
    // 异步操作成功后执行
    console.log(data);
    console.log(data2)
}, function(error) {
    // 异步操作失败后执行
    console.log(error);
});

【输出结果:(等待三秒)】
第一个回调调用成功
第二个回调调用成功

关于 Promise 的一些简单理解

(2)$.ajax()
$.ajax() 可以理解成一个 受限的 Deferred 对象,也即前面提到的 不能改变异步状态的 Deferred 对象(没有 resolve、reject 方法)。
虽然使用起来与 Deferred 类似,但是 $.ajax() 语法糖还是有些许不同。比如:ajax 中的 success、error、complete 方法分别对应 deferred 中的 done、fail、always 方法。

关于 Promise 的一些简单理解
【基本写法:】
$.ajax({
    url: "https://www.cnblogs.com/l-y-h/",
    success: function(data) {
        console.log("成功");
        console.log(data);
    },
    error: function(error) {
        console.log("失败");
        console.log(error)
    },
    complete: function() {
        console.log("总是执行");
    }
})

【链式写法:】
$.ajax("https://www.cnblogs.com/l-y-h/").success(function(data) {
    console.log("成功");
    console.log(data);
}).error(function(error) {
    console.log("失败");
    console.log(error)
}).complete(function() {
    console.log("总是执行");
});

【链式写法:(等同于上一种链式写法)】
$.ajax("https://www.cnblogs.com/l-y-h/").done(function(data) {
    console.log("成功");
    console.log(data);
}).fail(function(error) {
    console.log("失败");
    console.log(error)
}).always(function() {
    console.log("总是执行");
});

Original: https://www.cnblogs.com/l-y-h/p/13819752.html
Author: 累成一条狗
Title: 关于 Promise 的一些简单理解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/598428/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 2020年12月-第02阶段-前端基础-CSS Day06

    CSS Day06 定位(position) *理解 能说出为什么要用定位能说出定位的4种分类能说出四种定位的各自特点能说出我们为什么常用子绝父相布局 *应用 能写出淘宝轮播图布局…

    Linux 2023年6月8日
    0114
  • Nuget私服老是弹输入用户名密码框

    先把vs中私服删除,然后,控制面板\用户帐户\凭据管理器,删除私服 最后输入命令: NuGet.exe Sources Update -Name <feedname> …

    Linux 2023年6月13日
    097
  • 解决Conda改源后无法安装软件包的问题

    前言 有时候哪怕修改了 Conda 源也一直无法安装一个想要的软件包,亦或者找到了目标软件包,下载速度却很慢,速度感人,也可能直接 Conda 就找不到你想安装的软件包 此时有两种…

    Linux 2023年6月14日
    080
  • 2020年12月-第02阶段-前端基础-CSS Day07

    CSS Day07 CSS高级技巧 *理解 能说出元素显示隐藏最常见的写法能说出精灵图产生的目的能说出去除图片底侧空白缝隙的方法 *应用 能写出最常见的鼠标样式能使用精灵图技术能用…

    Linux 2023年6月8日
    0109
  • Redis 为什么使用单进程单线程方式也这么快(转载)

    Redis 采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由 C 语言编写。官方提供的数据是可以达到100000+的 qps。这个数据不比采用单进程多线程的同样基于内…

    Linux 2023年5月28日
    072
  • VMware ESXi 7.0 U3 SLIC 2.6 & Unlocker 集成 Intel NUC 网卡、USB 网卡和 NVMe 驱动

    提供标准版和 Dell (戴尔)、HPE (慧与)、Lenovo (联想)、Inspur (浪潮)、Cisco (思科) 定制版镜像 请访问原文链接:VMware ESXi 7.0…

    Linux 2023年5月27日
    0152
  • 玩转SpringBoot之捣鼓 Redis

    我们都知道,把首页数据放到Redis里,能够加快首页数据的访问速度。但是我们要如何准确又快速的将 Redis 整合到自己的 SpringBoot2.x 项目中呢?今天阿淼就带大家爬…

    Linux 2023年5月28日
    0102
  • Linux网络编程基础API

    Linux网络API主要可分为: socket地址API socket基础API 网络信息API socket地址API 两种字节序 大端字节序:整数的高位字节存储在内存的低地址处…

    Linux 2023年6月13日
    063
  • CentOS7下安装mysql8.0.25

    一、mysql的rpm包准备 官网下载完整rpm包 解压后有多个rpm包, 挑选如下图的5个rpm包上传至linux 二、使用rpm -ivh安装rpm包 按如下安装顺序依次安装 …

    Linux 2023年6月6日
    0120
  • Linux备份之远程同步—rsync

    一、备份 1.1 什么是备份? 备份就是把重要的数据或者文件复制一份保存到另一个地方,实现不同主机之间的数据同步 1.2 为什么做备份? 数据在公司中是很重要的!!!备份就是为了恢…

    Linux 2023年5月27日
    092
  • 最小生成树-Kruskal算法

    与 Prim算法贪心选择不同,Kruskal算法采取 每次选择权值最小的边的方法,这样,在 不构成环且最后能够连接完所有边它们的权重和一定是最小的。 和之前Prim算法的图一样,便…

    Linux 2023年6月7日
    0102
  • Linux基线加固

    bash;gutter:true; 1、修改vsftp回显信息 (1)检查办法 修改vsftp回显信息: 需在安装VSFTP的情况下检查,未安装可忽略或禁用该项。 查看ftpd_b…

    Linux 2023年6月13日
    080
  • IDEA生成带参数和返回值注释

    步骤说明 打开IDEA进入点击左上角 – 文件 – 设置 – 编辑器 – 活动模板 新建活动模板 填写模板文本 编辑变量 添加变量表…

    Linux 2023年6月6日
    094
  • SUPERVISOR监控tomcat配置文件

    下方为Supervisor管理tomcat的配置,多注意红色位置路径修改: [program:tomcat] ; 管理的子程序名字,要和项目有关联,不能乱写 command=/us…

    Linux 2023年6月6日
    082
  • Linux 下 SVN 的安装和配置

    SVN 是一个自由开源的版本管理系统,它可以按照时间的顺序去管理文件、目录以及对其进行的修改。于今,它被广泛的用于互联网公司的项目版本管理中 工作原理 它的工作原理如下图所示 它是…

    Linux 2023年6月13日
    089
  • Linux软件包常见的几种下载、安装方法

    如果服务器是处于在线状态,在使用默认下载源是外国的情况下,安装更新软件包往往会比较痛苦的存在,下载了许久来一个超时就gg了。国内有许多镜像源,完美的解决了这个问题。 对于rpm系列…

    Linux 2023年6月6日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球