Promise晋级—完全吃透

栏目: JavaScript · 发布时间: 5年前

内容简介:基本的promise使用,读本文需要了解基本的查兼容性 基本上 主流浏览器支持没有问题。

Promise晋级,需要的全部都在这

主要内容:

  1. promise基本实现原理
  2. promise 使用中难点(链式调用,API基本上返回都是一个新Promise,及参数传递)
  3. promise 对异常处理
  4. promise 简单实现及规范

参考:

30分钟,让你彻底明白Promise原理

阮一峰ES6入门

JavaScript Promise:简介

0. 基本用法

基本的promise使用,读本文需要了解基本的 Promise 使用。

1. 兼容性

查看caniuse

Promise晋级—完全吃透

查兼容性 基本上 主流浏览器支持没有问题。

IE不兼容 问题,本文不予以处理,出门左转,找谷哥。具体查看babel,或者 自己实现一个Promise

2. ajax XMLHttpRequest封装

//get 请求封装
function get(url) {
  // Return a new promise.
  return new Promise(function(resolve, reject) {
    // Do the usual XHR stuff
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      // This is called even on 404 etc
      // so check the status
      if (req.status == 200) {
        // Resolve the promise with the response text
        resolve(req.response);
      }
      else {
        // Otherwise reject with the status text
        // which will hopefully be a meaningful error
        reject(Error(req.statusText));
      }
    };

    // Handle network errors
    req.onerror = function() {
      reject(Error("Network Error"));
    };

    // Make the request
    req.send();
  });
}
复制代码

1. Promse API

Promise API 分为 :MDN

  1. 静态方法

  2. prototype 上方法

    Promise.prototype.then() 来分析

    首先来看看 `Promise.prototype.then()`返回一个`Promise`,但`Promise`内部有返回值,且 返回值,可以是个值,也可能就是一个新`Promise`
    
    *具体规则如下:*
    复制代码
    • 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
    • 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
    • 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
    • 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
    • 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。

    上面是官方规则,神马,具体白话就是 核心是 返回参数及返回promise的状态

    参考:MDN

    是不是 觉得很晕,没关系,可以先看 下一节,看完后,再回过来看具体的说明

    /*then 回调中,
    	1. 返回是return function,则返回一个Promise 【参见对比3代码】
    	2. 不是一个function,则 then 将创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态。(MDN中解释)【参见对比1代码】
    	3. 返回一个function,但没有return ,则相当于 then(null)
      */
    //对比1 穿透问题  返回是'foo' 而不是 'bar'
    Promise.resolve('foo').then(Promise.resolve('bar')).then(function(result){
        console.log(result)
    })
    
    
    //对比2  打印undefined
    Promise.resolve('foo').then(function(){Promise.resolve('bar')}).then(function(result){
        console.log(result)
    })
     
    
    //对比3  返回 'bar'
    Promise.resolve('foo').then(function() {
        return Promise.resolve('bar')
    }).then(function(result) {
        console.log(result)
    })
    复制代码

2. Prmise 链式调用

链式调用

  1. 核心就是 then catch 等方法返回一个Promise
  2. 链式 调用数据传递(注意)

1. 值传递问题

简单例子

//正常状态
const promise1 = new Promise((resolve, reject) => {
    resolve('0000')//
})
promise1.then(result => {
    console.log(result) //0000
	   return '1111';//类似于 return Promise.resolve('1111'); 参数是data,promise 状态时 resolve
}).then(data => {
    console.log(data) // 1111
})
复制代码

一个实际的例子:(拿来大神的例子 JavaScript Promise:简介

get('story.json').then(function(response) {
  console.log("Success!", response);
})
复制代码
//这里的 response 是 JSON,但是我们当前收到的是其纯文本。也可以设置XMLHttpRequest.responseType =json
get('story.json').then(function(response) {
  return JSON.parse(response);
}).then(function(response) {
  console.log("Yey JSON!", response);
})
复制代码
//由于 JSON.parse() 采用单一参数并返回改变的值,因此我们可以将其简化为:
get('story.json').then(JSON.parse).then(function(response) {
  console.log("Yey JSON!", response);
})
复制代码
function getJSON(url) {
  return get(url).then(JSON.parse);
}
//getJSON() 仍返回一个 promise,该 promise 获取 URL 后将 response 解析为 JSON。
复制代码

2. 异步操作队列

上面至今是 return 值 ,直接调用 下一下 then 就OK了。

但如果 return Promise ,则?

Promise.resolve(111).then(function(d){
	console.log(d);
	return Promise.resolve(d+111);//返回promise
}).then(function(d2){
	console.log(d2);
})
// 111,222
复制代码

3. 并行问题forEach处理

当多个异步并行执行时,每个异步代码执行时间不定,所以多个异步执行结束时间无法确定(无法确定结束完时间)。

所以需要特殊处理。

//forEach 顺便无法保证
var arrs = [1,2,3,4];
var p = function(d){
	return new Promise((resolve)=>{
       setTimeout(()=>{
			resolve(d);
		},Math.random()*1000);//因为异步执行时间无法确认
    });
};
arrs.forEach(function(arr){
  p(arr).then((d)=>{
    console.log(d);
  })
});
复制代码
//使用 Promise.all 来让返回有序
var arrs = [1,2,3,4];
var p = function(d){
	return new Promise((resolve)=>{
       setTimeout(()=>{
			resolve(d);
		},Math.random()*1000);//因为异步执行时间无法确认
    });
};
var ps = [];
arrs.forEach(function(arr){
  ps.push(p(arr));
});
Promise.all(ps).then(values=>{
  console.log(values);//[1,2,3,4]
})
复制代码

4. 基本实现原理—实现一个简单Promise

自己手撸一个简单的 Promise

1. 版本1—极简实现

//版本1 极简实现
function Promise1(fn) {
 var value = null,
     callbacks = [];  //callbacks为数组,因为可能同时有很多个回调

 this.then = function (onFulfilled) {
     callbacks.push(onFulfilled);
     return this;//支持链式调用 Promise.then().then
 };

 function resolve(value) {
     callbacks.forEach(function (callback) {
         callback(value);
     });
 }

 fn(resolve);
}
//Test 对上面实现,写一个简单的测试
new Promise1(function(resolve){
 setTimeout(function(){
     resolve(1);
 },100);
}).then(function(d){
 console.log(d);
})
//1
复制代码

2. 版本2—加入延时机制

//上面版本1 可能导致问题
//在then注册回调之前,resolve就已经执行了
new Promise1(function(resolve){
    console.log(0)
	resolve(1);
}).then(function(d){
   console.log(d);
})
// 1 不会打印
复制代码
//版本2 解决
function Promise1(fn) {
    var value = null,
        callbacks = [];  //callbacks为数组,因为可能同时有很多个回调

    this.then = function (onFulfilled) {
        callbacks.push(onFulfilled);
        return this;//支持链式调用 Promise.then().then
    };

    function resolve(value) {
       setTimeout(function(){
        callbacks.forEach(function (callback) {
            callback(value);
        }),0});
    }

    fn(resolve);
}
复制代码

3. 版本3—状态

Promise 有三种状态 pendingfulfilledrejected ,且状态变化时单向的。

具体细节就是 在 then , resolve 中加状态判断,具体代码略

4. Promises/A+

具体 Promise 实现有一套官方规范,具体参见Promises/A+

5. finnaly 实现

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. 异常处理

异常分类:

try-catch

1. Promise 异常处理基本套路

基本处理异常中,有两种方案 then(undefined, func)catch()

then(undefined, func)catch() 不同 ,具体参见代码 方案3

//方案1 使用 Promise.prototype.catch()来catch
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永远不会执行
}).catch(error => {
    console.log(error) // no
})
复制代码
//方案2 使用 Promise.prototype.then()中第二个参数 来处理
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永远不会执行
},error => {
    console.log(error) // no
})
复制代码
//方案2  (方案1  方案2 对比)
var promise2 = new Promise((resolve, reject) => {
    resolve('yes')// 
})
promise2.then(result => {
    throw new Error('then');
    console.log(result) 
},error => {
    console.log('1111',error) // no
}).catch(error=>{
   console.log('2222',error)// 最终 err在此处被捕获,而不是 then 中
})
复制代码

2. 异常不同分类

Promise可能遇到的异常种类

//1.异常 reject()
const promise1 = new Promise((resolve, reject) => {
    reject('no')// 
})
promise1.then(result => {
    console.log(result) // 永远不会执行
}).catch(error => {
    console.log(error) // no
})

复制代码
//2.异常 显示throw
const promise1 = new Promise((resolve, reject) => {
    throw Error('no')
})
promise1.then(result => {
    console.log(result) // 永远不会执行
}).catch(error => {
    console.log(error) // 
})
复制代码
//3.执行异常
const promise1 = new Promise((resolve, reject) => {
    aaaa;
})
promise1.then(result => {
    console.log(result) // 永远不会执行
}).catch(error => {
    console.log(error) // 
})
复制代码

3. 异常链式调用

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})
复制代码

上述代码的流程图形式:

Promise晋级—完全吃透
// promise链式调用,catch住异常后,后面就不会处理异常了
Promise.reject().then(()=>{
  console.log(2222);
},(err)=>{
	console.log(333,err)
	return err})
.catch((err)=>{
  console.log(1111,err);
})
//333 undefined  ,没有打印 1111
复制代码
//如果 在链式调用中,then 第二个参数 catch住了异常,没有return Promise.reject()则后续链式调用返回rosolve状态pormise
Promise.reject()
   .then(()=>{
      console.log(111);
    },(err)=>{
        console.log(111,err) //reject 
        return err;
    }).then((data)=>{
        console.log(222,data);//resolve 执行
    },(err)=>{
      console.log(222,err); //未执行
    })
//4444 没有执行 1111
复制代码

4. 异常丢失

很多情况下,promise无法捕获异常

场景1macrotask 队列中抛出异常:

//场景1
//永远不要在 macrotask 队列中抛出异常,因为 macrotask 队列脱离了运行上下文环境,异常无法被当前作用域捕获。
function fetch(callback) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
             throw Error('用户不存在')
        })
    })
}

fetch().then(result => {
    console.log('请求处理', result) // 永远不会执行
}).catch(error => {
    console.log('请求处理异常', error) // 永远不会执行
})

// 程序崩溃
// Uncaught Error: 用户不存在

/*
    参考
    作者:黄子毅
    链接:https://www.jianshu.com/p/78dfb38ac3d7
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
*/
复制代码
//解决场景1 怎么解决,因为setTimeout 是macrotask任务,执行上下文完全不同
/**
	如何解决?
	调用reject
*/
function fetch() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject('收敛一些')
        })
    })
}
fetch().then((resolve, reject) => {
    console.log('resolve');
}).catch(error => {
    console.log('捕获异常', error) // 捕获异常 收敛一些
})
复制代码

场景二Promise 状态只能改变一次

//异常丢失
   const promise2 = new Promise((resolve, reject) => {
       reject('no')
       console.log('reject after')
     throw Error('no') //异常丢失
   })
   promise1.then(result => {
       console.log(result) // 永远不会执行
   }).catch(error => {
       console.log('err',error) // no
   }).catch(error => {
       console.log('err2',error) // 也无法捕获异常
   })
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Refactoring

Refactoring

Martin Fowler、Kent Beck、John Brant、William Opdyke、Don Roberts / Addison-Wesley Professional / 1999-7-8 / USD 64.99

Refactoring is about improving the design of existing code. It is the process of changing a software system in such a way that it does not alter the external behavior of the code, yet improves its int......一起来看看 《Refactoring》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换