记录学习Promise

174 阅读7分钟

同步与异步

JavaScript的执行环境是「单线程」,同步模式,即上述所说的单线程模式,一次只能执行一个任务,异步模式,可以一起执行多个任务,函数调用后不会立即返回执行的结果,如果任务A需要等待,可先执行任务B,等到任务A结果返回后再继续回调。

回调函数

常见的异步回调即setTimeout,ajax请求。可理解为(执行完)回(来)调(用)的函数。

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

Promise接受一个「函数」作为参数,该函数的两个参数分别是resolve和reject。这两个函数就是就是「回调函数」,由JavaScript引擎提供。

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

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

了解完这些基础知识后,可以直接通过代码来深入了解了。

下面就是很多栗子...

例一:Promise 三种状态

var p1 = new Promise(function(resolve,reject){
  resolve(1);
});
var p2 = new Promise(function(resolve,reject){
  setTimeout(function(){
    resolve(2);  
  }, 500);      
});
var p3 = new Promise(function(resolve,reject){
  setTimeout(function(){
    reject(3);  
  }, 500);      
});

console.log(p1);
console.log(p2);
console.log(p3);
setTimeout(function(){
  console.log(p2);
}, 1000);
setTimeout(function(){
  console.log(p3);
}, 1000);

p1.then(function(value){
  console.log(value);
});
p2.then(function(value){
  console.log(value);
});
p3.catch(function(err){
  console.log(err);
});

控制台:

解析:p1执行的是同步代码,所以直接执行,输出p1是resolved状态,值为 1。p2,p3中有异步所以会先处于pending状态。接着我们通过settimeout函数再延迟1s后输出,此时p2、p3已经执行完成,状态分别变成resolved和rejected。

第二个栗子 Promise 状态的不可逆性

var p1 = new Promise(function(resolve, reject){
  resolve("success1");
  resolve("success2");
});

var p2 = new Promise(function(resolve, reject){
  resolve("success3");
  reject("reject1");
});

p1.then(function(value){
  console.log(value);
});

p2.then(function(value){
  console.log(value);
});

console中的结果:

解析:Promise状态一旦变成resolved或rejected时,Promise的状态和值就固定下来了,不论你后续再怎么调用resolve或reject方法,都不能改变它的状态和值。因此,p1中resolve("success2")并不能将p1的值更改为success2,p2中reject("reject1")也不能将p2的状态由resolved改变为rejected.

链式调用(非常重要,通过此例可以理解then返回对象)

  var p = new Promise(function(resolve, reject){
        resolve(1);
    });
    p.then(function(value){     //第一个then
        console.log(value);     //1
        return value*2;         //返回状态为resolved的promise对象,值是2
    }).then(function(value){    //第二个then
        console.log(value);     //所以值是2
    }).then(function(value){    //第三个then
        console.log(value);         //因为第二个then中无返回值,所以输出undefined
        return Promise.resolve('resolve');
    }).then(function(value){     //第四个then
        console.log(value);       //打印出resolve 因为第三个then返回了resolved状态的Promise对象,Promise对象的值就是这个返回值。
        return Promise.reject('reject');
    }).then(function(value){              //第五个then
        //console.log('resolve: '+ value); //同上 因为返回的是reject 所以打印出reject
     }, function(err){
         console.log('reject: ' + err);
    })

console的结果:

解析:Promise对象的then方法返回一个新的Promise对象,因此可以通过链式调用then方法。 then方法接收两个函数作为参数,第一个参数是Promise执行成功时的回调,第二个参数是Promise执行失败时的回调。两个函数只会有一个被调用,函数的返回值将被用作创建then返回的Promise对象。

这两个参数的返回值可以是以下三种情况中的一种:

return 一个同步的值 ,或者 undefined(当前一个then没有返回一个有效值时,默认返回undefined),then方法将返回一个resolved状态的Promise对象,Promise对象的值就是这个返回值。

return 另一个 Promise,then方法将根据这个Promise的状态和值创建一个新的Promise对象返回。

throw 一个同步异常,then方法将返回一个rejected状态的Promise, 值是该异常。

具体分析见代码中注释部分。

Promise 中的异常

var p1 = new Promise( function(resolve,reject){
  foo.bar();  //最先执行,但undefined
  resolve( 1 );	  
});

p1.then(
  function(value){
    console.log('p1 then value: ' + value);
  },
  function(err){
    console.log('p1 then err: ' + err);  //step1:有错所以执行err,打印出异常信息
  }
).then(
  function(value){
    console.log('p1 then then value: '+value); //step3
  },
  function(err){
    console.log('p1 then then err: ' + err);
  }
);

var p2 = new Promise(function(resolve,reject){
  resolve( 2 );	
});

p2.then(                 
  function(value){
    console.log('p2 then value: ' + value); //step2:p1的异常得到处理,可以正常执行了
    foo.bar();  
  }, 
  function(err){
    console.log('p2 then err: ' + err);
  }
).then(
  function(value){
    console.log('p2 then then value: ' + value);
  },
  function(err){
    console.log('p2 then then err: ' + err);
    return 1;
  }
).then(
  function(value){
    console.log('p2 then then then value: ' + value);
  },
  function(err){
    console.log('p2 then then then err: ' + err);
  }
);

console结果:

解析:Promise中的异常由then参数中(而不是下一个then)第二个回调函数(Promise执行失败的回调)处理异常信息将作为Promise的值。异常一旦得到处理,then返回的后续Promise对象将恢复正常,并会被Promise执行成功的回调函数处理。另外,需要注意p1、p2 多级then的回调函数是交替执行的 ,这正是由Promise then回调的异步性决定的。

Promise.resolve()

var p1 = Promise.resolve( 1 );
var p2 = Promise.resolve( p1 );
var p3 = new Promise(function(resolve, reject){
  resolve(1);
});
var p4 = new Promise(function(resolve, reject){
  resolve(p1);
});

console.log(p1 === p2); 
console.log(p1 === p3);
console.log(p1 === p4);
console.log(p3 === p4);

p4.then(function(value){
  console.log('p4=' + value);
});

p2.then(function(value){
  console.log('p2=' + value);
})

p1.then(function(value){
  console.log('p1=' + value);
})

console结果:

解析:Promise.resolve(...)可以接收一个值或者是一个Promise对象作为参数。当参数是普通值时,它返回一个resolved状态的Promise对象,对象的值就是这个参数;当参数是一个Promise对象时,它直接返回这个Promise参数。因此,p1 === p2。但通过new的方式创建的Promise对象都是一个新的对象,因此后面的三个比较结果都是false。

总结!!

首先,Promise是一个对象,代表了未来某时间才会知道结果的时间,不受外界因素的印象。Promise一旦触发,其状态只能变为fulfilled或者rejected,并且已经改变不可逆转。Promise的构造函数接受一个函数作为参数,该参数函数的两个参数分别为resolve和reject,其作用分别是将Promise的状态由pending转化为fulfilled或者rejected,并且将成功或者失败的返回值传递出去。then有两个函数作为Promise状态改变时的回调函数,当Promise状态改变时接受传递来的参数并调用相应的函数。then中的回调的过程为异步操作。

catch方法是对.then(null,rejectFn)的封装(语法糖),用于指定发生错误时的回调函数。一般来说,不要再then中定义rejected状态的回调函数,应该使用catch方法代替。all和race都是竞速函数,all结束的时间取决于最慢的那个,其作为参数的Promise函数一旦有一个状态为rejected,则总的Promise的状态就为rejected;而race结束的时间取决于最快的那个,一旦最快的那个Promise状态发生改变,那个其总的Promise的状态就变成相应的状态,其余的参数Promise还是会继续进行的。

另外:关于promise then回调函数异步性这部分,我没有写出来,但是这部分很重要。可以直接去看js事件执行机制,把这一块放一起了解会更好。

代码例子参考链接:juejin.cn/post/684490…