关于 Promise 的一些简单理解

时间:2020-10-15 11:56:00 来源:互联网 热度: 作者: 佚名 字体:

一、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 等方法。

 

 

(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() 等。通过这些方法接收、传递 回调函数 从而实现 异步调用。

 

 

(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);
});

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

 

 

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

 

 

【基本写法:】
$.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("总是执行");
});