JavaScriptES6系列-Promise 对象详解

转载请注明预见才能遇见的博客:https://blog.csdn.net/pcaxb

原文地址:https://blog.csdn.net/pcaxb/article/details/102587763

JavaScriptES6系列-Promise 对象详解

目录

JavaScriptES6系列-Promise 对象详解

1.Promise 的含义

2.基本用法

3.Promise.prototype.then()

4.Promise.prototype.catch()

5.Promise.prototype.finally()

6.Promise.all()

7.Promise.race()

8.Promise.allSettled() ES2020引入

9.Promise.any() 提案

10.Promise.resolve()

(1)参数是一个 Promise 实例

(2)参数是一个thenable对象

(3)参数不是具有then方法的对象,或根本就不是对象

(4)不带有任何参数

11.Promise.reject()

12.应用

(1)加载图片 

(2)Generator 函数与 Promise 的结合

13.Promise.try() 提案


1.Promise 的含义

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。Promise在ES6之前就有,只是ES6 将其写进了语言标准,统一了用法。Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise无法中途取消,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

Promise对象有以下两个特点:

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意:本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

2.基本用法

const promise = new Promise(function(resolve,reject) {
   let async = true;//异步操作成功
   if(async){
       resolve("成功");
       console.log("调用resolve或reject并不会终结 Promise 的参数函数的执行");//打印
   }else{
       reject("失败");
   }
});

//第一个参数是成功回调,第二个参数是失败回调(可选)
promise.then(function(res) {
   log(res);
},function(error) {
   log(error);
});

//推荐使用catche,不推荐使用then的第二个参数作为失败的回调
promise.then(function(res) {//成功
    
  }).catch(function(error) {//失败
    console.log("error="+error)
  });

一个函数作为Promise的参数,函数的参数是两个函数resolve和reject,所以名字当然也是随便取哦。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

//1
function timeout(ms) {
   return new Promise((resolve, reject) => {
     //setTimeout第三个参数是第一个参数函数的参数
     setTimeout(resolve, ms, 'done');
   });
}
timeout(100).then((value) => {
   console.log(value);
});

//2
let promise = new Promise(function(resolve, reject) {
   console.log('Promise');
   resolve();
});
//then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行
promise.then(function() {
   console.log('resolved.');
});

console.log('Hi!');
// 'Promise' 然后 'Hi!' 然后 'resolved.'

//3
const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('fail')), 3000)
})

const p2 = new Promise(function (resolve, reject) {
    setTimeout(() => resolve(p1), 40000)
})

p2//这里的result不是p1而是p1的结果
.then(result => console.log(result))
.catch(error => console.log(error))
// Error: fail

一般来说,调用resolvereject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolvereject的后面。所以,最好在它们前面加上return语句,这样就不会有意外。

3.Promise.prototype.then()

then方法是定义在原型对象Promise.prototype上的,它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。

const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject("11111111"), 1000)
});
const p2 = new Promise(function (resolve, reject) {
    setTimeout(() => reject("22222222"), 1000)
});

p1.then(function(res) {
    console.log("success");
    return res;
    //使用return可以给下一个then传值
},function(error) {
    console.log("fail");
    return error;
}).then(function(result) {
    console.log(result);
    return p2;
    //如果return返回的是promise,下一个then就会有成功和失败的接收
}).then(function(res) {
    console.log("success");
    console.log(res);
},function(error) {
    console.log("fail");
    console.log(error);
})

then方法返回的是一个新的Promise实例,因此可以采用链式写法,即then方法后面再调用另一个then方法。第一个回调函数完成以后,使用return会将返回结果作为参数,传入第二个回调函数。

4.Promise.prototype.catch()

Promise.prototype.catch方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

//1
const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject("11111111"), 1000)
});
p1.catch(function(error) {
    console.log(error);//11111111
})

//2
const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => resolve("11111111"), 1000)
});
p1.then(function(res) {
    console.log("成功");
    console.log(a);//错误
},function(params) {
    console.log(params+"===");
    console.log(a);//错误
})
.catch(function(params) {
    console.log(params + "===")//成功和失败回调函数有错误会调用
})
//3
const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject("11111111"), 1000)
});
p1.then(function(res) {
    console.log("成功");
},function(params) {
    console.log(params+"===");
    return "kkkkkkk"
})
.catch(function(params) {
    console.log(params + "===")//错误时候使用return,不会调用
})

//4
p1.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p1.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));

//5
const promise = new Promise(function(resolve, reject) {
  resolve('ok');
  throw new Error('test');
});
promise
  .then(function(value) { console.log(value) })
  .catch(function(error) { console.log(error) });
// ok

如果异步操作抛出错误,或者then方法指定的回调函数在运行中抛出错误会被catch方法捕获。或者使用reject方法,等同于抛出错误。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

const p1 = new Promise(function (resolve, reject) {
    reject("111")
});

p1.then(function(res) {
    
  }).then(function(res) {
    
  },function(error) {
      //如果没有这个回调函数就会执行后面的catch回调函数
      console.log("fail="+error)
  }).catch(function(error) {
    console.log("error="+error)
  });

Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”

const p1 = new Promise(function (resolve, reject) {
    console.log(a);//报错
});

setTimeout(function(params) {
    console.log(1111);//Promise报错,程序不会停止,后面的依然会执行
},2000)

catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。如果没有报错,则会跳过catch方法。

const p1 = new Promise(function (resolve, reject) {
    resolve("1");
});

p1.then(function(res) {
    console.log("1");
}).then(function(res) {
    console.log("2");
}).catch(function(error) {
    console.log("3");
}).then(function(res) {
    console.log("4");
});//1 2 4  如果没有报错,则会跳过catch方法
//说明 then 是可以一级一级传递的,不管有没有return给下一个then传参数

catch方法也可以抛出一个错误,如果后面没有别的catch方法了,就会导致这个错误不会被捕获,也不会传递到外层。

5.Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。finally方法的回调函数不接受任何参数。

const p1 = new Promise(function (resolve, reject) {
    resolve("1");
});
p1.finally(function() {
    //回调函数没有参数,任何状态都会执行
})

finally的实现

Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};

6.Promise.all()

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。参数是promise数组或者是promise类数组

//创建Promise工厂
function createPromise(id) {
    return new Promise(function(resolve,reject) {
        setTimeout(resolve,1000,id)
    })
}

//通过map把一个数组生成一个新的数组
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
    return createPromise(id);
});

var p = Promise.all(promises);
p.then(function (res) {
    //promises中的所有promise的状态都变成fulfilled,p的状态才会变成fulfilled,
    //此时promises中的所有promise的返回结果组成一个数组,传递给p的resolve回调函数
    console.log(res);//[2, 3, 5, 7, 11, 13]
}).catch(function(error){
    //只要promises中有一个promise的状态变成rejected,P的状态就会变成rejected,
    //此时,第一个被rejected的返回结果就会唱给p的reject回调函数
});

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

上面代码中,p1resolvedp2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

7.Promise.race()

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

const p = Promise.race([
    fetch('/resource-that-may-take-a-while'),
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);
  
p.then(console.log)
.catch(console.error);

上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

8.Promise.allSettled() ES2020引入

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.allSettled([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});

上面代码中,Promise.allSettled()的返回值allSettledPromise,状态只可能变成fulfilled。它的监听函数接收到的参数是数组results。该数组的每个成员都是一个对象,对应传入Promise.allSettled()的两个 Promise 实例。每个对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejectedfulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。

有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,Promise.allSettled()方法就很有用。

9.Promise.any() 提案

Promise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。

const resolved = Promise.resolve(42);
const rejected = Promise.reject(-1);

const allSettledPromise = Promise.any([resolved, rejected]);

allSettledPromise.then(function (results) {
  console.log(results);
});
allSettledPromise.catch(function (results) {
  console.log(results);
});

Promise.any()Promise.race()方法很像,只有一点不同,就是不会因为某个 Promise 变成rejected状态而结束。

10.Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve()方法就起到这个作用。

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

(1)参数是一个 Promise 实例

如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

(2)参数是一个thenable对象

thenable对象指的是具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

let thenable = {//thenable对象
  then: function(resolve, reject) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
});

上面代码中,thenable对象的then方法执行后,对象p1的状态就变为resolved,从而立即执行最后那个then方法指定的回调函数,输出 42。

(3)参数不是具有then方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved

const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hell

Promise.resolve方法的参数,会同时传给回调函数。

(4)不带有任何参数

需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。

etTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行,因此最先输出。

11.Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
const thenable = {
  then(resolve, reject) {
    reject('出错了');
  }
};

Promise.reject(thenable)
.catch(e => {
  console.log(e === thenable)
})
// true

注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

12.应用

(1)加载图片 

const preloadImage = function (path) {
  return new Promise(function (resolve, reject) {
    const image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};

(2)Generator 函数与 Promise 的结合

function getFoo () {
  return new Promise(function (resolve, reject){
    resolve('foo');
  });
}

const g = function* () {
  try {
    const foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run (generator) {
  const it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(function (value) {
      return go(it.next(value));
    }, function (error) {
      return go(it.throw(error));
    });
  }

  go(it.next());
}

run(g);

13.Promise.try() 提案

实际开发中,经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法。

Promise.resolve().then(f)

上面的写法有一个缺点,就是如果f是同步函数,那么它会在本轮事件循环的末尾执行。

const f = () => console.log('now');
Promise.resolve().then(f);
console.log('next');
// next
// now

上面代码中,函数f是同步的,但是用 Promise 包装了以后,就变成异步执行了。那么有没有一种方法,让同步函数同步执行,异步函数异步执行,并且让它们具有统一的 API 呢?回答是可以的,并且还有两种写法。

第一种写法是用async函数来写。

const f = () => console.log('now');
(async () => f())();
console.log('next');
// now
// next

上面代码中,第二行是一个立即执行的匿名函数,会立即执行里面的async函数,因此如果f是同步的,就会得到同步的结果;如果f是异步的,就可以用then指定下一步,就像下面的写法。

(async () => f())()
.then(...)

需要注意的是,async () => f()会吃掉f()抛出的错误。所以,如果想捕获错误,要使用promise.catch方法。

(async () => f())()
.then(...)
.catch(...)

第二种写法是使用new Promise()

const f = () => console.log('now');
(
  () => new Promise(
    resolve => resolve(f())
  )
)();
console.log('next');
// now
// next

提供Promise.try方法替代上面的写法。

const f = () => console.log('now');
Promise.try(f);
console.log('next');
// now
// next

由于Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。这样有许多好处,其中一点就是可以更好地管理异常。

function getUsername(userId) {
  return database.users.get({id: userId})
  .then(function(user) {
    return user.name;
  });
}

上面代码中,database.users.get()返回一个 Promise 对象,如果抛出异步错误,可以用catch方法捕获,就像下面这样写。

database.users.get({id: userId})
.then(...)
.catch(...)

但是database.users.get()可能还会抛出同步错误(比如数据库连接错误,具体要看实现方法),这时你就不得不用try...catch去捕获。

try {
  database.users.get({id: userId})
  .then(...)
  .catch(...)
} catch (e) {
  // ...
}

上面这样的写法就很笨拙了,这时就可以统一用promise.catch()捕获所有同步和异步的错误。

Promise.try(() => database.users.get({id: userId}))
  .then(...)
  .catch(...)

事实上,Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块。

 

JavaScriptES6系列-Promise 对象详解

博客地址:https://blog.csdn.net/pcaxb/article/details/102587763

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值