美文网首页方法Web前端之路让前端飞
高级前端人员必会之 - Promise

高级前端人员必会之 - Promise

作者: 果汁凉茶丶 | 来源:发表于2017-11-16 20:33 被阅读260次

    编后吐槽:目前最用心的一篇,写的快花眼,很详细,耐心看必受益匪浅

    JavaScript的执行环境是「单线程」的。所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程。但实际上还有其他线程,如事件触发线程、ajax请求线程等。因为javascript的单线程原理,使得网络操作,浏览器事件,都必须是异步执行的。

    * 同步与异步

    • 同步:同步模式,即上述所说的单线程模式,一次只能执行一个任务,函数调用后需等到函数执行结束,返回执行的结果,才能进行下一个任务。如果这个任务执行的时间较长,就会导致「线程阻塞」。
    var x = true;
    while(x);
    console.log("don‘t’ carry out");
    // javascript 单线程原理导致同步执行,第三步不被执行
    
    • 异步:可以一起执行多个任务,函数调用后不会立即返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后再继续回调。
    // 常见异步,定时器的使用
    setTimeout(function() {
      console.log('taskA, run as asynchronous');
    }, 0)
    console.log('taskB, run as synchronize');
    //while(true);
    
    // taskB, run as synchronize
    // taskA, run as asynchronous
    

     即使延时时长为0,taskA依旧晚于taskB;因为定时器是异步的,异步任务会在当前脚本所有同步任务完成之后才被执行。,如果同步任务中含有阻塞任务,即放开代码中的while(true),那么taskA将不会被执行。

    * 什么是Promise

      古人云:“君子一诺千金”,这种“承诺某个状态将来会执行,并且该状态不会被改变”的对象在JavaScript中称为Promise对象,他是一个构造函数,能将异步的操作以同步的形式表达出来,避免了嵌套地狱(层层嵌套)的发生。它供了统一的API,使得控制异步更加容易

    * 回调函数

     回调函数是一段可执行的代码段,它以「参数」的形式传递给其他代码,在其合适的时间执行这段(回调函数)的代码。
     异步可以回调,同步也可以回调;

    // 同步回调
    var func1 = function(cb) {
      // ..do something
      console.log("before callback");
      (cb && typeof(cb) === 'function') && cb();
      console.log("after callback");
    }
    var func2 = function(param) {
      // ..do something
      var start = new Date();
      if(( new Date() - start) < 3000 ){ }  // 同步实现延时函数
      console.log("I am callback");
    }
    
    func1(func2);
    
    ------output------- 
    // before callback
    (after 3s...)
    // I am callback
    // after callback
    

     由于是同步回调,会阻塞后面的代码,如果func2是个死循环,后面的代码就不执行了。为解决这个问题,我们使用异步回调,除了常见的setTimeoutajax也是应用方式之一

    // 异步回调
    function request(url, param, successFun, errorFunc) {
      $.ajax({
        type: 'GET',
        url: url,
        param: param,
        async: true,  // 默认为true,如果设置为false则变成同步请求
        success: successFunc,
        error: errorFunc
      });
    }
    request('test.html', '', function(data) {
      // 第三个参数为返回成功时执行的函数
      console.log('cbData: ', data);
    }, function(data) {
      // 第四个参数为失败时的函数
      console.log('error: ', error);
    });
    

    * 为什么要用Promise

    既然已经可以实现异步回调,那我们为什么还要用Promise呢?
    javascript代码

    function callback () {
      console.log("Done");
    }
    
    console.log("before setTimeout()")
    setTimeout(callback, 1000);
    console.log("after setTimeout()"); 
    

    控制台输出

    before setTimeout()
    after setTimeout()
    (等待一秒后...)
    Done
    

    说明: setTimeout()延时函数时javascript实现异步执行的手段之一,该例中它将callback放到等待队列,并且开始为延时量计时,当到达延时量主线程中的事件还没执行完成,那等待队列中的事件继续保持等待,直到主线程的执行完成才被加入到线程中执行。所以代码中说的”等待一秒后”其实并不准确。

     可见,异步操作会在将来的某个时间点触发一个函数调用。
     如果我们有这样一个需求:下一个请求或函数必须要有上一步返回的数据才能执行,如下:

    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不仅能以同步的方式表示异步操作,还能catch到异常,这是ajax无法实现的

    * Promise in JS

    1. 基本结构

    结构示意图

    ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
    javascript代码

    var pm = new Promise(function(resolve, reject) {
      // ..some code
      if( /*异步操作结果*/) {
        resolve(value);
      } else {
        reject(error);
      }
    })
    
    pm.then(function(){
      // handle resolveFunc
    },function() {
      // handle rejectFunc
    })
    

    这就是它的基本模型,为了继续往下学习,我们需要补充一下Promise的一些基础知识。

    2. Promise 三种状态

    Promise链式调用用到resolverejectthencatch,他有以下三种状态

    • pending - 进行中,或者等待中,表示还没有得到结果
    • fulfilled - 已成功,在异步操作成功时调用,并将结果作为参数传递出去。
    • rejected - 已失败。在异步操作失败时调用,并将报出的错误作为参数传递出去。

    只有异步操作的结果可以决定当前是哪种状态,其他任何操作都无法改变这个状态,这也是Promise名字的由来。所谓的“君子一言,驷马难追”,承诺将来某个状态,且该状态不会被其他因素改变。

    注释: 从基本用法的例子中我们看到Promise构造函数的参数是resolvereject,并不是三种状态中的fulfilledrejected,原因就是:resolved表示的是已结束(已定型),它包含fullfilledrejected两种状态,但使用中,我们默认的将resolved当做fulfilled(成功)使用。

    3. Promise 对象的特点

    (1)对象的状态不受外界因素影响。以上已经说过。
    (2)一旦结果状态生成,就不会在改变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种状态发生了,状态就凝固不变,并一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这和事件监听有很大的区别,事件监听是实时的,如果错过了监听时机,就得不到要监听的结果了

    promise也有缺点
    • 无法取消Promise,一旦新建它就会立即执行,无法中途取消
    • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部(所以健壮的代码要catch错误)
    • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

    * 基本API

    1 .then()

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

    这是Promise最常用也是最重要的一个API,他定义了Promise的两个回调函数,并返回一个新的Promise实例,且返回值传入这个行Promise的resolve函数中。
    因此,我们可以使用链式写法,如为什么要用Promise中的最后一例,返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

    2. .catch() - 抛出异常

    语法: Promise.prototype.catch( onRejected )

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

    var promise = new Promise(function(resolve, reject)){
      // some code
    }
    
    promise.then(function(data)  {
      console.log('success');
    }, function(error) {
      conosle.log('error', error)
    })
    
    /*---等价于---*/
    promise.then(function(data){
      console.log('success');
    }).catch(function(error) {
      consol;e.log('error', error)
    })
    

    再看一例

    var promise = new Promise(function(resoleve, reject) {
      throw new Error('test');
    });
    // 等同于
    var promise = new Promise(resovle, reject) {
      reject(new Error('test'));
    }
    
    // 以上的reject我们用catch来捕获
    promise.catch(function (error) {
      console.log(error);
    });
    ---output---
    Error: test
    

     从上例可以看出,reject方法的作用,等同于抛错,这是Promise另一大优势

     关于Promise抛错有几下几个需要注意的地方

    1. Promise对象的错误,会一直向后传递,知道被捕获,即错误总会被下一个catch所捕获。then方法指定的回调函数,若抛出错误,也会被下一个catch捕获。catch中也能抛错,则需要后面的catch来捕获。
     sendRequest('test.html').then(function(data1) {
        //do something
    }).then(function (data2) {
        //do something
    }).catch(function (error) {
        //处理前面三个Promise产生的错误
    });
    
    1. 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
    });
    
    1. 如果没有使用catch方法指定处理错误的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应(Chrome会抛错),这是Promise的另一个缺点。
    var promise = new Promise(function(resolve, reject) {
      resolve();  // 状态已经被返回为resolve
      throw 'error';
    });
    
    promise.catch(function(e) {
       console.log(e);      //This is never called
    });
    

    抛错实例:

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

    Chrome上的表现;


    状态为reject且控制台报错

      据说只有chrome会报错,其他浏览器的错误不会被捕获,也不会传递到外层代码,最后没有任何输出,promise的状态也变为rejected

    3 .all() - Promise中的“逻辑与”,同时开始,并行执行

    语法: 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的回调函数
    var p1 = new Promise(function(resolve, reject) {
      setTimeout(resolve, 3000, "first");
    });
    var p2 = new Promise(function(resolve, reject) {
      resolve("second");
    });
    var p3 = new Promise(function(resolve, reject) {
      setTimeout(resolve, 1000, "third");
    });
    
    Promise.all([p1, p2, p3]).then(function(values) {
      console.log(values);
    });
     
    // ----output----
    // 约3秒后
    // ["first", "second", "third"]
    
    1. 当p1, p2, p3其中之一状态变为rejected,p的状态也会变为rejected,并把第一个被reject的promise的返回值,立即触发并传给p的回调函数
    // 将上例中的p2适当修改如下
    var p2 = new Promise(function(resolve, reject) {
      resolve(x);
    });
    

    这时,p2会抛出错误,立即传给Promise.all(),结束执行。

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

    4 .race() - 竞速执行,Promise中“逻辑或”,先结束的传值给 then

    语法: Promise.race( iterable )

    该方法同样是将多个Promise实例,包装成一个新的Promise实例。

    var p = Promise.race([p1, p2, p3]);
    
    1. Promise.race方法同样接受一个数组(或具有Iterator接口)作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。
      执行resolve
    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
    

    执行reject

    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
    
    1. 在第一个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     //仍会执行
    

    5 .resolve() - 立即执行Promise-resolve

    语法: 1. Promise.resolve(value); 2. Promise.resolve(promise); 3. Promise.resolve(thenable);

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

    new Promise(function (resolve) {
        resolve('Success');
    });
    //----等同于----
    Promise.resolve('Success');
    
    1. 这段代码会让这个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
    });
    
    1. 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
    });
    
    1. 无论是在什么时候抛异常,只要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
    });
    
    6 .reject() - 立即执行Promise-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中的.then方法,可以接收构造函数中处理的状态的变化,并且分别对应执行。.then有两个函数参数,分别接收resolvedrejected的执行。
     简单来说, then就是定义resolvereject函数的,其resolve函数相当于:

    function resolveFun(data) {
      // data 为 promise中resolve函数中所带的参数
    }
    

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

    1. 执行顺序

    javascript代码

    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
    
    2.调用延时执行函数
    function timeout (ms) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve, ms, 'done');   // 三个参数?往下看
      })
    }
    
    timeout(100).then((value) => {
      console.log(value)
    })
    
    // 输出 done
    

     该例写了一个延时调用函数,设置ms时间以后,才将Promise的状态修改为resolve,然后执行.then中的打印操作。
     这里埋了个点,比较有意思。当给setTimeout()传入大于两个参数时,从第三个开始代表的是传给延时执行函数的参数,想具体了解的可以戳这里

    3. 异步加载图片
    function loadImgAsync (url) {
      return new Promise(function (resolve, reject) {
        var img = new Image();
        
        image.onload = function () {
          resolve(image);
        };
        image.onerror = function() {
          reject(new Error('Could not load image at ' + url));
        }
        image.src = url;
      })
    }
    

     上诉的例子中,先加载图片,如果图片加载成功,就调用resolve,否则调用reject

    4. 用Promise实现一个Ajax操作
    var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';
    
    // 封装一个get请求的方法
    function getJSON(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) {
                    if (XHR.status == 200) {
                        try {
                            var response = JSON.parse(XHR.responseText);
                            resolve(response);
                        } catch (e) {
                            reject(e);
                        }
                    } else {
                        reject(new Error(XHR.statusText));
                    }
                }
            }
        })
    }
    
    getJSON(url).then(resp => console.log(resp));
    

     为了健壮性,处理了很多可能出现的异常,总之,成功了就resolve,失败了就reject。
     如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例

    现在所有的库几乎都将ajax请求利用Promise进行了封装,因此我们在使用jQuery等库中的ajax请求时,都可以利用Promise来让我们的代码更加优雅和简单。这也是Promise最常用的一个场景

    5. resolve参数为另一个Promise实例 - 难点
    var p1 = new Promise(function(resolve, reject) {
      // ..some code
    })
    var p2 = new Promise(function(resolve, reject){
      // ..some code
      resolve(p1)
    })
    

      上述中,p1和p2都是一个实例,定义了之后都立即执行,但是p2的resolve方法将p1作为参数,即一个异步操作的结果是返回另一个异步操作
    注意:这时,p1的状态决定了p2的状态,如果p1的状态是pending,那么,p2的回调函数就会等待p1状态的改变,如果p1的状态已经是resolve或rejected,那么p2就会立即被执行。

    很典型的一个例子

    var p1 = new Promise(function(resolve, reject){
      setTimeout(() => reject(new Error('Fail')), 3000)
    })
    var p2 = new Promise(function(resolve, reject) {
      setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => conosle.log(result))
    .catch(error => console.log(error)) 
    //  建议大家看一下浏览器输出效果
    // 输出 Error: fail
    

    分析
     上述代码中,p1和p2被定义后都立即异步执行,p1执行3秒之后返回reject,p2执行1秒后返回resolve,但p2的返回结果是p1,所以,又要等待2秒p1返回结果,即p2的返回结果是“p1的返回结果”,这样大家就明白了,最终执行的其实是p1的结果reject,所以p2这里调用.catch而不是.then

    注意:调用resolvereject并不会终结Promise的参数函数的执行。

    new Promise(function(resolve, reject) {
      resolve(1);
      console.log(2);
    }).then(val => {
      console.log(val)
    }) 
    
    //  2
    //  1
    

     上面代码中,调用resolve(1)以后,后面的console.log(2)还是会执行,并且会首先打印出来。这是因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。虽然有这种策略,但我们一般习惯还是将其放在最后。

    6. 聊天系统获取两个用户的信息 - Promise.all()
    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 600, 'P2');
    });
    // 同时执行p1和p2,并在它们都完成后执行then:
    Promise.all([p1, p2]).then(function (results) {
        console.log(results); // 获得一个Array: ['P1', 'P2']
    });
    
    7. 多个异步任务提高容错率 - Promise.race()
    var p1 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 500, 'P1');
    });
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(resolve, 600, 'P2');
    });
    Promise.race([p1, p2]).then(function (result) {
        console.log(result); // 'P1'
    });
    

    由于p1执行较快,Promise的then()将获得结果'P1'。p2仍在继续执行,但执行结果将被丢弃。

    * Promise常见问题

    到这里,相信你已学会使用Promise了,congratulation!

    1. reject 和 catch 的区别

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

    综上所述,建议使用第二种,因为能捕获之前的所有异常。当然了,第二种的.catch()也可以使用.then()的捕错法表示,它们本质上是没有区别的,.catch === .then(null, onRejected)

    2. 如果在then中抛错,而没有对错误进行处理(即catch),那么会一直保持reject状态,直到catch了错误
    /* 例4.1 */
    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)    // 抛出错误,不继续Task A”
        .then(taskB)   // .then没有捕获A抛出的错,不打印 “Task B”
        .catch(onRejected)  // 捕获了A的错,打印错误信息
        .then(finalTask);   // 错误已经被捕获,执行resolve
        
    -------output-------
    Catch Error: A or B,ReferenceError: x is not defined
    Final Task
    

    来看一下该例的流程


    很明显,A抛错时,会按照taskA → onRejected → finalTask这个流程来处理。A抛错后,若没有对它进行处理,状态就会维持rejected,taskB不会执行,直到catch了错误。
    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
    

    then的调用几乎是同时开始执行的,且传给每个then的value都是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
    

    或许上面这个案例你还没感觉,来看看这个:

    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;
        });
        return promise;
    }
    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状态变为resove或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
    

    花了两天工作之余的时间,总算写完了~~看了很多资源,借鉴了许多观点和例子,希望能帮到大家
    参考资源:
    阮一峰的ES6 教程
    廖雪峰的官方网站
    Promise迷你书
    MDN Promise
    廖雪峰的官网能直接在里面自定义代码尝试运行,挺有意思。

    相关文章

      网友评论

      • codecraftlab:💪果断关注了博主!写的非常用心!
        果汁凉茶丶:@codecraftlab 蟹蟹,时快时慢的哈,工作忙的时候就慢些~
        codecraftlab:@果汁凉茶丶 你更博的效率好高,怎么完成的
        果汁凉茶丶:@codecraftlab 蟹蟹,一起学习一起进步

      本文标题:高级前端人员必会之 - Promise

      本文链接:https://www.haomeiwen.com/subject/oyxtvxtx.html