zoukankan      html  css  js  c++  java
  • 学习Promise笔记

    什么是Promise?

    MDN对Promise的定义:Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作。

    在学习Promise之前得先了解同步与异步:
    JavaScript的执行环境是单线程。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会阻塞其他任务。这个任务可称为主线程。

    但实际上还有其他线程,如事件触发线程,Ajax请求线程等。

    同步:

    同步模式,即上述所说的单线程模式,一次只能执行一个任务,函数调用后需要等到函数执行结束,返回执行结果,才能进行下一个任务。如果这个任务执行的时间较长,就会导致线程阻塞。

    var x = true;
    while(x);
    console.log("don't carry out"); // 不会执行

    上面代码中的while是一个死循环,它会阻塞进程,因此第三句console不会执行。

    异步:
    异步模式,即与同步模式相反,可以一起执行多个任务,函数调用后不会立即执行返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后继续回调。

    最常见的异步模式就是定时器的使用:

    setTimeout(function() {
        console.log('taskA, asynchronous');
    }, 0);
    console.log('taskB, synchronize');
    //while(true);
    
    -------ouput-------
    taskB, synchronize
    taskA, asynchronous

    虽然定时器延时的时间为0,但taskA还是晚于taskB执行。这是因为定时器是异步的,异步任务会再当前脚本的所有同步任务执行完后才会执行。如果同步代码中含有死循环,则这个异步任务不会执行,因为同步任务阻塞了进程。

    回调函数:

    上例中,setTimeout里的function便是回调函数。可以理解为:(执行完)回调的函数。

    WikiPedia对callback的定义可以理解为:回调函数是一段可执行的代码段,它以参数的形式传递给其他代码,在其合适的时间执行这段(回调函数)的代码。回调函数不仅是可以用于异步调用,一般同步的场景也可以用回调。在同步调用下,可能一段时间后执行执行或不执行(未达到执行的条件)

    /******************同步回调******************/
    var fun1 = function(callback) {
        //do something
        console.log("before callback");
        (callback && typeof(callback) === 'function') && callback();
        console.log("after callback");
    }
    var fun2 = function(param) {
        //do something
        var start = new Date();
        while((new Date() - start) < 3000) { //delay 3s
        }
        console.log("I'm callback");
    }
    fun1(fun2);
    
    -------output--------
    before callback
    //after 3s
    I’m callback
    after callback

    由于是同步调用,会阻塞后面的代码,如果fun2是个死循环,后面的代码就不执行了。

    除了上面setTimeout为常见的异步回调,另外常见的异步即Ajax请求:

    /******************异步回调******************/
    function request(url, param, successFun, errorFun) {
        $.ajax({
            type: 'GET',
            url: url,
            param: param,
            async: true,    //默认为true,即异步请求;false为同步请求
            success: successFun,
            error: errorFun
        });
    }
    request('test.html', '', function(data) {
        //请求成功后的回调函数,通常是对请求回来的数据进行处理
        console.log('请求成功啦, 这是返回的数据:', data);
    },function(error) {
        console.log('sorry, 请求失败了, 这是失败信息:', error);
    });

    为什么使用Promise

    利用Promise改写上面Ajax的例子:

    function sendRequest(url, param) {
        return new Promise(function (resolve, reject) {
            request(url, param, resolve, reject);
        });
    }
    
    sendRequest('test.html', '').then(function(data) {
        //异步操作成功后的回调
        console.log('请求成功啦, 这是返回的数据:', data);
    }, function(error) {
        //异步操作失败后的回调
        console.log('sorry, 请求失败了, 这是失败信息:', error);
    });

    Promise的优势在于它的重链式调用,可以避免层层嵌套回调。如果第一次Ajax请求后,还可以用它的返回的结果再次请求

    request('test1.html', '', function(data1) {
        console.log('第一次请求成功, 这是返回的数据:', data1);
        request('test2.html', data1, function (data2) {
            console.log('第二次请求成功, 这是返回的数据:', data2);
            request('test3.html', data2, function (data3) {
                console.log('第三次请求成功, 这是返回的数据:', data3);
                //request... 继续请求
            }, function(error3) {
                console.log('第三次请求失败, 这是失败信息:', error3);
            });
        }, function(error2) {
            console.log('第二次请求失败, 这是失败信息:', error2);
        });
    }, function(error1) {
        console.log('第一次请求失败, 这是失败信息:', error1);
    });

    以上出现了多层调用,难以明白层级之间的关系,这就是常说的回调地狱(Pyramid of Doom),而使用Promise,可以利用then进行链式调用,将异步操作以同步操作的流程表示出来。

    sendRequest('test1.html', '').then(function(data1) {
        console.log('第一次请求成功, 这是返回的数据:', data1);
    }).then(function(data2) {
        console.log('第二次请求成功, 这是返回的数据:', data2);
    }).then(function(data3) {
        console.log('第三次请求成功, 这是返回的数据:', data3);
    }).catch(function(error) {
        //用catch捕捉前面的错误
        console.log('sorry, 请求失败了, 这是失败信息:', error);
    });

    Promise的基本用法

    Promise对象代表一个未完成、但预计将来会完成的操作。它有以下三种状态:

    pending:初始值,不是fulfilled,也不是rejected

    fulfilled:代表操作成功

    rejected:代表操作失败

    Promise有两种状态改变的方式,既可以从pending转变为fulfilled,也可以从pending转变为rejected。一旦状态改变,就会一直保持这个状态。当状态发生变化,Promise.then绑定的函数就会被调用。

    注意:Promise一旦新建就会立即执行,无法取消。这也是它的缺点之一。

    下面通过一个例子进一步讲解:

    //构建Promise
    var promise = new Promise(function (resolve, reject) {
        if (/* 异步操作成功 */) {
            resolve(data);
        } else {
            /* 异步操作失败 */
            reject(error);
        }
    });

    类似构建对象,我们使用new来构建一个Promise。Promise接受一个函数作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是回调函数,由JavaScript引擎提供。

    resolve函数的作用:在异步操作成功时调用,并将异步操作的结果,作为参会素传递出去;

    reject函数的作用:在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    Promise实例生成以后,可以用then方法指定resolve和reject状态的回调函数。

    promise.then(onFulfilled, onRejected);
    
    promise.then(function(data) {
      // do something when success
    }, function(error) {
      // do something when failure
    });

    then方法会返回一个Promise。它有两个参数,分别为Promise从pending变为fulfilled和rejected时的回调函数(第二个参数非必选)。这两个函数都接受Promise对象传出的值作为参数。

    简单来说,then就是定义resolve和reject函数的,其resolve参数相当于:

    function resolveFun(data) {
        //data为promise传出的值
    }

    而新建的Promise中的‘resolve(data)’,则相当于执行resolveFun函数。

    Promise新建后就会立即执行。而then方法中指定的回调函数,将在当前脚本所有同步任务执行完才会执行。如下例:

    var promise = new Promise(function(resolve, reject) {
      console.log('before resolved');
      resolve();
      console.log('after resolved');
    });
    
    promise.then(function() {
      console.log('resolved');
    });
    
    console.log('outer');
    
    -------output-------
    before resolved
    after resolved
    outer
    resolved

    由于resolve指定的是异步操作成功后的回调函数,它需要等所有的同步代码执行后才会执行,因此最后打印‘resolved’

    基本API

    .then()

    语法:Promise.prototype.then(onFulfilled,onRejected)

    对Promise添加onFulfilled和onRejected回调,并返回的是一个新的Promise实例(不是原来那个Promise实例),且返回值将作为参数传入这个新Promise的resolve函数。

    因此可以使用链式写法。由于前一个回调涵涵素,返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

    .catch()

    语法:Promise.prototype.catch(onRejected)

    该方法是.then(undefined,onRejected)的别名,用于指定发生错误时的回调函数。

    promise.then(function(data) {
        console.log('success');
    }).catch(function(error) {
        console.log('error', error);
    });
    
    /*******等同于*******/
    promise.then(function(data) {
        console.log('success');
    }).then(undefined, function(error) {
        console.log('error', error);
    });
    var promise = new Promise(function (resolve, reject) {
        throw new Error('test');
    });
    /*******等同于*******/
    var promise = new Promise(function (resolve, reject) {
        reject(new Error('test'));
    });
    
    //用catch捕获
    promise.catch(function (error) {
        console.log(error);
    });
    -------output-------
    Error: test

    从上例可知,reject方法的作用等同于抛错。

    promise对象的错误,会一直向后传递,直到被捕获。即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。

    sendRequest('test.html').then(function(data1) {
        //do something
    }).then(function (data2) {
        //do something
    }).catch(function (error) {
        //处理前面三个Promise产生的错误
    });

    上面提到的,promise状态一旦改变就会凝固,不会再改变。因此promise一旦fulfilled了,再抛错,也不会变为rejected,就不会被catch了

    var promise = new Promise(function(resolve, reject) {
      resolve();
      throw 'error';
    });
    
    promise.catch(function(e) {
       console.log(e);      //This is never called
    });
    var promise = new Promise(function(resolve, reject) {
      resolve();
      throw 'error';
    });
    
    promise.catch(function(e) {
       console.log(e);      //This is never called
    });

    如果没有使用catch方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点。

    var promise = new Promise(function (resolve, reject) {
        resolve(x);
    });
    promise.then(function (data) {
        console.log(data);
    });

    如图所示,只有Chrome会抛错,且promise状态变为rejected,Firefox和Safari中错误不会被捕获,也不会传递到外层代码,最后没有任何输出,promise状态也变为rejected。

    .all()

    语法:Promise.all(iterable)

    该方法用于将多个Promise实例,包装成一个新的Promise实例。

    var p = Promise.all([p1, p2, p3]);

    Promise.all方法接受一个数组(或具有Iterator接口)作参数,数组中的对象(p1,p2,p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例来决定。

    1.当p1,p2,p3状态都为fulfilled,p的状态才会变为fulfilled,并将三个promise返回的结果,按参数的顺序(而不是resolved的顺序)存入数组,传给p的回调函数;

    /* 例3.8 */
    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 3000, "first");
    });
    var p2 = new Promise(function (resolve, reject) {
        resolve('second');
    });
    var p3 = new Promise((resolve, reject) => {
      setTimeout(resolve, 1000, "third");
    }); 
    
    Promise.all([p1, p2, p3]).then(function(values) { 
      console.log(values); 
    });
    
    -------output-------
    //约 3s 后
    ["first", "second", "third"] 

    2.当p1,p2,p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被rejected的promise的返回值,传给p的回调函数;

    var p1 = new Promise((resolve, reject) => { 
      setTimeout(resolve, 1000, "one"); 
    }); 
    var p2 = new Promise((resolve, reject) => { 
      setTimeout(reject, 2000, "two"); 
    });
    var p3 = new Promise((resolve, reject) => {
      reject("three");
    });
    
    Promise.all([p1, p2, p3]).then(function (value) {
        console.log('resolve', value);
    }, function (error) {
        console.log('reject', error);    // => reject three
    });
    
    -------output-------
    reject three

    这多个promise是同时开始、并行执行的,而不是顺序执行的。

    function timerPromisefy(delay) {
        return new Promise(function (resolve) {
            setTimeout(function () {
                resolve(delay);
            }, delay);
        });
    }
    var startDate = Date.now();
    
    Promise.all([
        timerPromisefy(1),
        timerPromisefy(32),
        timerPromisefy(64),
        timerPromisefy(128)
    ]).then(function (values) {
        console.log(Date.now() - startDate + 'ms');
        console.log(values);
    });
    -------output-------
    133ms       //不一定,但大于128ms
    [1,32,64,128]

    .race()

    语法:Promise.race(iterable)

    该方法同样接受一个数组(或具有Iterator接口)作参数。当p1,p2,p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。

    var p1 = new Promise(function(resolve, reject) { 
        setTimeout(reject, 500, "one"); 
    });
    var p2 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, "two"); 
    });
    
    Promise.race([p1, p2]).then(function(value) {
        console.log('resolve', value); 
    }, function(error) {
        //not called
        console.log('reject', error); 
    });
    -------output-------
    resolve two
    
    var p3 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, "three");
    });
    var p4 = new Promise(function(resolve, reject) { 
        setTimeout(reject, 100, "four"); 
    });
    
    Promise.race([p3, p4]).then(function(value) {
        //not called
        console.log('resolve', value);              
    }, function(error) {
        console.log('reject', error); 
    });
    -------output-------
    reject four

    在第一个promise对象变为resolve后,并不会取消其他promise对象的执行,如下

    var fastPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log('fastPromise');
            resolve('resolve fastPromise');
        }, 100);
    });
    var slowPromise = new Promise(function (resolve) {
        setTimeout(function () {
            console.log('slowPromise');
            resolve('resolve slowPromise');
        }, 1000);
    });
    // 第一个promise变为resolve后程序停止
    Promise.race([fastPromise, slowPromise]).then(function (value) {
        console.log(value);    // => resolve fastPromise
    });
    -------output-------
    fastPromise
    resolve fastPromise
    slowPromise     //仍会执行

    .resolve()

    语法:

    Promise.resolve(value);

    Promise.resolve(promise);

    Promise.resolve(thenable);

    它可以看做new Promise()的快捷方式

    Promise.resolve('Success');
    
    /*******等同于*******/
    new Promise(function (resolve) {
        resolve('Success');
    });

    这段代码会让这个Promise对象立即进入resolved状态,并将结果success传递给then指定的onFulfilled回调函数。由于Promise.resolve()也是返回Promise对象,因此可以用.then()处理其返回值。

    Promise.resolve('success').then(function (value) {
        console.log(value);
    });
    -------output-------
    success
    //Resolving an array
    Promise.resolve([1,2,3]).then(function(value) {
      console.log(value[0]);    // => 1
    });
    
    //Resolving a Promise
    var p1 = Promise.resolve('this is p1');
    var p2 = Promise.resolve(p1);
    p2.then(function (value) {
        console.log(value);     // => this is p1
    });

    Promise.resolve()的另一个作用就是将thenable对象(即带有then方向的对象)转换为promise对象。

    var p1 = Promise.resolve({ 
        then: function (resolve, reject) { 
            resolve("this is an thenable object!");
        }
    });
    console.log(p1 instanceof Promise);     // => true
    
    p1.then(function(value) {
        console.log(value);     // => this is an thenable object!
      }, function(e) {
        //not called
    });

    下面两个例子,无论是在什么时候抛异常,只要promise状态变成resolved或rejected,状态不会再改变,这和新建promise是一样的。

    //在回调函数前抛异常
    var p1 = { 
        then: function(resolve) {
          throw new Error("error");
          resolve("Resolved");
        }
    };
    
    var p2 = Promise.resolve(p1);
    p2.then(function(value) {
        //not called
    }, function(error) {
        console.log(error);       // => Error: error
    });
    
    //在回调函数后抛异常
    var p3 = { 
        then: function(resolve) {
            resolve("Resolved");
            throw new Error("error");
        }
    };
    
    var p4 = Promise.resolve(p3);
    p4.then(function(value) {
        console.log(value);     // => Resolved
    }, function(error) {
        //not called
    });

    .reject()

    语法:Promise.reject(reason)

    这个方法和上述的Promise.resolve()类似,它也是new Promise()的快捷方式。

    Promise.reject(new Error('error'));
    
    /*******等同于*******/
    new Promise(function (resolve, reject) {
        reject(new Error('error'));
    });

    这段代码会让这个Promise对象立即进入rejected状态,并将错误对象传递给then指定的onRejected回调函数。

    Promise常见问题

    总结一下创建promise的流程:

    1.使用new Promise(fn)或者它的快捷方式Promise.resolve()、Promise.reject,返回一个promise对象

    2.在fn中指定异步的处理

    处理结果正常,调用resolve

    处理结果错误,调用reject

    如果使用ES6的箭头函数,将会使写法更加简单清晰

    接下来用例子说明promise使用过程中的注意点及容易犯的错误。

    情景1:reject和catch的区别

    promise.then(onFulfilled,onRejected)  在onFulfilled中发生异常的话,在onRejected中是捕获不到这个异常的。

    promise.then(onFilfilled).catch(onRejected) .then中产生的异常能在.catch中捕获

    一般情况,使用第二种,第二种的.catch()也可以使用.then()表示,它们本质上没有区别,.catch===.then(null,onRejected)

    情景2:如果在then中抛错,而没有对错进行处理(即catch),那么会一直保持reject状态,直到catch了错误

    function taskA() {
        console.log(x);
        console.log("Task A");
    }
    function taskB() {
        console.log("Task B");
    }
    function onRejected(error) {
        console.log("Catch Error: A or B", error);
    }
    function finalTask() {
        console.log("Final Task");
    }
    var promise = Promise.resolve();
    promise
        .then(taskA)
        .then(taskB)
        .catch(onRejected)
        .then(finalTask);
        
    -------output-------
    Catch Error: A or B,ReferenceError: x is not defined
    Final Task

    从代码的输出结果及流程,可以看出,A抛错时,会按照taskA->onRejected->finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejected,taskB不会执行,直到catch了错误。

    function taskA() {
        console.log(x);
        console.log("Task A");
    }
    function taskB() {
        console.log("Task B");
    }
    function onRejectedA(error) {
        console.log("Catch Error: A", error);
    }
    function onRejectedB(error) {
        console.log("Catch Error: B", error);
    }
    function finalTask() {
        console.log("Final Task");
    }
    var promise = Promise.resolve();
    promise
        .then(taskA)
        .catch(onRejectedA)
        .then(taskB)
        .catch(onRejectedB)
        .then(finalTask);
        
    -------output-------
    Catch Error: A ReferenceError: x is not defined
    Task B
    Final Task

    在TaskA后多了对A的处理,因此,A抛错时,会按照taskA->onRejectedA->taskB->finalTask这个流程来处理,此时taskB是正常执行的。

    情景3:每次调用then都会返回一个新创建的promise对象,而then内部只是返回的数据

    //方法1:对同一个promise对象同时调用 then 方法
    var p1 = new Promise(function (resolve) {
        resolve(100);
    });
    p1.then(function (value) {
        return value * 2;
    });
    p1.then(function (value) {
        return value * 2;
    });
    p1.then(function (value) {
        console.log("finally: " + value);
    });
    -------output-------
    finally: 100
    
    //方法2:对 then 进行 promise chain 方式进行调用
    var p2 = new Promise(function (resolve) {
        resolve(100);
    });
    p2.then(function (value) {
        return value * 2;
    }).then(function (value) {
        return value * 2;
    }).then(function (value) {
        console.log("finally: " + value);
    });
    -------output-------
    finally: 400

    第一中方法中,then的调用几乎是同时开始执行的,且传给每个then的value都是100,这种方法应当避免。方法二才是正确的链式调用。

    容易出现下面的错误写法:

    function badAsyncCall(data) {
        var promise = Promise.resolve(data);
        promise.then(function(value) {
            //do something
            return value + 1;
        });
        return promise;
    }
    badAsyncCall(10).then(function(value) {
       console.log(value);          //想要得到11,实际输出10
    });
    -------output-------
    10

    正确的写法:

    function goodAsyncCall(data) {
        var promise = Promise.resolve(data);
        return promise.then(function(value) {
            //do something
            return value + 1;
        });
    }
    goodAsyncCall(10).then(function(value) {
       console.log(value);
    });
    -------output-------
    11

    情景4:在异步回调中抛错,不会被catch到

    // Errors thrown inside asynchronous functions will act like uncaught errors
    var promise = new Promise(function(resolve, reject) {
      setTimeout(function() {
        throw 'Uncaught Exception!';
      }, 1000);
    });
    
    promise.catch(function(e) {
      console.log(e);       //This is never called
    });

    情景5:promise状态变为resolve或reject,就凝固了,不会再改变

    console.log(1);
    new Promise(function (resolve, reject){
        reject();
        setTimeout(function (){
            resolve();            //not called
        }, 0);
    }).then(function(){
        console.log(2);
    }, function(){
        console.log(3);
    });
    console.log(4);
    
    -------output-------
    1
    4
    3
  • 相关阅读:
    171 01 Android 零基础入门 03 Java常用工具类02 Java包装类 01 包装类简介 01 Java包装类内容简介
    170 01 Android 零基础入门 03 Java常用工具类01 Java异常 08 Java异常总结 01 异常总结
    169 01 Android 零基础入门 03 Java常用工具类01 Java异常 07 异常链 01 异常链简介
    168 01 Android 零基础入门 03 Java常用工具类01 Java异常 06 自定义异常 01 自定义异常类
    167 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 02 使用throw抛出异常对象
    166 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 01 使用throws声明异常类型
    165 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 05 return关键字在异常处理中的使用
    DevExpress WPF v20.2版本亮点放送:全新升级的PDF Viewer
    界面控件DevExpress使用教程:Dashboard – 自定义导出
    DevExpress WinForms帮助文档:表单控件
  • 原文地址:https://www.cnblogs.com/lhh520/p/10217506.html
Copyright © 2011-2022 走看看