• Promise


    为什么要有Promise?

    Promise是异步编程的一种解决方案。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise对象提供了统一的接口,使得控制异步操作更加容易。

    目录结构:

    一、Promise的特点

    二、基本用法

    三、Promise的方法

    四、

    一、Promise的特点

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

    2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    二、基本用法

    const promise = new Promise(function(resolve, reject){
        // ... some code
    
        if(/*异步操作成功*/){
            resolve(value);
        }else{
            reject(error);
        }
    })

    Promise的构造函数参数是一个函数,该函数的两个参数分别是resolvereject,它们是两个函数,由JavaScript提供,不用自己部署。

    resolve函数的作用:将Promise对象的状态由pending变为resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态由pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去,这个参数是可选的。

    promise.then(function(value){
        //success
        }, function(error){ // 这个函数是可选的,不一定要提供
        //failure
    })

    使用Promise的栗子:Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

    function loadImageAsync(url) {
        return new Promise(function(resolve, reject){
            const image = new Image();
    
            image.onload = function(){
                resolve(image);
            };
    
            image.onerror = function(){
                reject(new Error('Could not load image at  '+url)
            }
    
            image.src = url
        })
    }

    用Promise对象实现Ajax操作

    const getJSON = function (url) {
        const promise = new Promise(function (resolve, reject) {
            const handler = function () {
                if (this.readyState !== 4) {
                    return;
                }
                if (this.status === 200) {
                    resolve(this.response)
                } else {
                    reject(new Error(this.statusText))
                }
            }
            const xhr = new XMLHttpRequest();
            xhr.open('GET', url);
            xhr.onreadystatechange = handler;
            xhr.responseType = 'json';
            xhr.setRequestHeader('Accept', 'application/json');
            xhr.send();
        });
        return promise;
    }
    
    getJSON('/posts.json').then(function (json) {
        console.log('Contents:' + json)
    }, function (error) {
        console.log('出错了', error)
    })

    getJSON内部,resolve函数和reject函数调用时,都带有参数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例。

    下图一、promise内部调用reject函数之后,在then方法内第二个参数函数里面输出抛出的错误。

    下图二、(同上)虽然在最后有catch语句,但是不会被调用。

    下图三、promise内部调用reject函数之后,没有then方法的第二个参数,那么会在catch方法里面输出抛出的错误。

    var promise = new Promise(function(resolve, reject){
        reject(new Error('123'))
    }).then(function(data){
        console.log(data)
    },function(error){
        console.log(1);
        console.log(error)
    })
    
    //输出:
    1
    Error: 123
    var promise = new Promise(function(resolve, reject){
        reject(new Error('123'))
    }).then(function(data){
        console.log(data)
    },function(error){
        console.log(1);
        console.log(error)
    }).catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出:
    1
    Error: 123
    var promise = new Promise(function(resolve, reject){
        reject(new Error('123'))
    }).then(function(data){
        console.log(data)
    }).catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出:
    2
    Error: 123

    如果在then方法里面的第二次参数里面return new promise,那么会在catch里面捕获错误

    var promise = new Promise(function(resolve, reject){
        reject(new Error('123'))
    }).then(function(data){
        console.log(data)
    },function(error){
        console.log(1);
        return new Promise(function(resolve, reject){reject(new Error('abc'))})
    }).catch(function(err){
        console.log(2);
        console.log(err)
    })
    //输出:
    1
    2
    Error: abc

    catch方法并没有捕获到then方法内的new Error错误

    var promise = new Promise(function(resolve, reject){
        resolve('123')
    }).then(function(data){
        console.log(data)
        console.log(new Error('error'));
    },function(error){
        console.log(1);
        
    }).catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出
    123
    Error: error

    并没有在catch方法里面输出error。

    var promise = new Promise(function(resolve, reject){
        resolve('123')
    }).then(function(data){
        console.log(data)
        return new Promise((resolve, reject)=>{new Error('error')});
    },function(error){
        console.log(1);
        
    }).catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出
    123

    只有promise里面调用resolve、reject函数才会在then方法里面有输出

    var promise = new Promise(function(resolve, reject){
        resolve('123')
    }).then(function(data){
        console.log(data)
        return new Promise((resolve, reject)=>{resolve(new Error('error'))});
    },function(error){
        console.log(1);
        
    }).then(function(data){console.log(data)})
    .catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出:
    123
    Error: error
    var promise = new Promise(function(resolve, reject){
        resolve('123')
    }).then(function(data){
        console.log(data)
        return new Promise((resolve, reject)=>{reject(new Error('error'))});
    },function(error){
        console.log(1);
        
    }).then(function(data){console.log(data)})
    .catch(function(err){
        console.log(2);
        console.log(err)
    })
    
    //输出:
    123
    2
    Error: error
    const p1 = new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('fail')), 3000)
    })
    const p2 = new Promise(function (resolve, reject) {
        setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => console.log(result))
        .catch(error => console.log(error))
    
    //输出
    Error: fail
    var p1 = new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('fail')), 0)
    })
    var p2 = new Promise(function (resolve, reject) {
        setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => console.log(result))
        .catch(error => console.log(error))
    
    //输出
    Promise {<pending>}
    Error: fail

    resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

    new Promise((resolve, reject) => {
      resolve(1);
      console.log(2);
    }).then(r => {
      console.log(r);
    });
    //输出
    2
    1
    new Promise(function (resolve, reject) {
        resolve('123')
    }).then(function (data) {
        console.log(data)
    }).then(function (data) {
        console.log(data)
    })
    
    //输出
    123
    undefined
    new Promise(function (resolve, reject) {
        resolve('123')
    }).then(function (data) {
        console.log(data)
        return new Promise(function (resolve, reject) {
            reject('234')
        })
    }).then(function (data) {
        console.log(data)
    }).catch(function (data) {
        console.log(data)
    })
    
    //输出
    123
    234

    三、Promise的方法

    1、Promise.prototype.then()

    then方法返回的是一个新的Promise实例,注意,不是原来那个Promise实例。因此可以采用链式写法,即then方法后面在调用另一个then方法。

    function getPromise(data) {
        return new Promise(function (resolve, reject) {
            resolve(data)
        })
    }
    getPromise({
        aaa: 'bbb'
    }).then(function (param) {
        return param
    }).then(function (param2) {
        console.log(param2.aaa)
    })
    
    //输出
    bbb

    上面的then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

    2、Promise.prototype.catch()

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

    getJSON('/posts.json').then(function(posts){
        // ...
    }).catch(function(error){
        // 处理getJSON 和 前一个回调函数运行时发生的错误
        console.log('发生错误!', error)
    })

    getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

    p.then((val) => console.log('fulfilled:', val))
      .catch((err) => console.log('rejected', err));
    
    // 等同于
    p.then((val) => console.log('fulfilled:', val))
      .then(null, (err) => console.log("rejected:", err));
    const promise = new Promise(function (resolve, reject) {
        throw new Error('test');
    });
    promise.catch(function (error) {
        console.log(error);
    });
    
    //输出
    Error: test

    上图等价于下图中的方法

    // 写法一
    const promise = new Promise(function(resolve, reject) {
      try {
        throw new Error('test');
      } catch(e) {
        reject(e);
      }
    });
    promise.catch(function(error) {
      console.log(error);
    });
    
    // 写法二
    const promise = new Promise(function(resolve, reject) {
      reject(new Error('test'));
    });
    promise.catch(function(error) {
      console.log(error);
    });

    reject方法的作用,等同于抛出错误。

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

    function getPromise(data) {
        return new Promise(function (resolve, reject) {
            resolve(data)
        })
    }
    getPromise('123').then(function (param) {
        return new Promise(function (resolve, reject) {
            reject(new Error('test1'))
        })
    }).then(function (param2) {
        return new Promise(function (rsolve, reject) {
            reject(new Error('test2'))
        })
    }).catch(function (data) {//处理前面三个Promise产生的错误
        console.log(data)
    })
    
    //输出
    Error: test1

    上面代码中,一共有三个Promise对象:一个由getPromise产生,两个由then产生,它们之中任何一个抛出的错误,都会被最后一个catch捕获。

    一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。建议总是使用catch方法,而不使用then方法的第二个参数。

    // bad
    promise
      .then(function(data) {
        // success
      }, function(err) {
        // error
      });
    
    // good
    promise
      .then(function(data) { //cb
        // success
      })
      .catch(function(err) {
        // error
      });

    跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

    const someAsyncThing = function () {
        return new Promise(function (resolve, reject) {
            // 下面一行会报错,因为x没有声明
            resolve(x + 2)
        })
    }
    someAsyncThing().then(function () {
        console.log('everything is great');
    })
    setTimeout(() => {
        console.log(123)
    }, 2000)
    
    //输出
    Uncaught (in promise) ReferenceError: x is not defined
    123

    someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

    const promise = new Promise(function (resolve, reject) {
      resolve('ok');
      setTimeout(function () { console.log(x+2) }, 0)
    });
    promise.then(function (value) { console.log(value) });
    
    //输出
    ok
    Uncaught ReferenceError: x is not defined

    上面代码中,Promise指定在下一轮“事件循环”再抛出错误,到那个时候,Promise的运行已经结束了,所以这个错误是在Promise函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

    一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

    const someAsyncThing = function() {
      return new Promise(function(resolve, reject) {
        // 下面一行会报错,因为x没有声明
        resolve(x + 2);
      });
    };
    
    someAsyncThing()
    .catch(function(error) {
      console.log('oh no', error);
    })
    .then(function() {
      console.log('carry on');
    });
    
    //输出
    oh no ReferenceError: x is not defined
    carry on

    上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。

    例如:

    Promise.resolve()
    .catch(function(error) {
      console.log('oh no', error);
    })
    .then(function() {
      console.log('carry on');
    });
    
    //输出
    carry on

    模糊的地方!!!

    var someAsyncThing = function() {
      return new Promise(function(resolve, reject) {
        // 下面一行会报错,因为x没有声明
        resolve(x + 2);
      });
    };
    
    someAsyncThing().then(function() {
        console.log('2222')
      return someOtherAsyncThing();
    }).catch(function(error) {
      console.log('oh no', error);
      // 下面一行会报错,因为 y 没有声明
      y + 2;
    }).then(function() {
      console.log('carry on');
    });
    
    //输出
    oh no ReferenceError: x is not defined
    Uncaught (in promise) ReferenceError: y is not defined

    上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。

    var someAsyncThing = function() {
      return new Promise(function(resolve, reject) {
        // 下面一行会报错,因为x没有声明
        resolve(x + 2);
      });
    };
    
    someAsyncThing().then(function() {
      return someOtherAsyncThing();
    }).catch(function(error) {
      console.log('oh no', error);
      // 下面一行会报错,因为y没有声明
      y + 2;
    }).catch(function(error) {
      console.log('carry on', error);
    });
    
    //输出
    oh no ReferenceError: x is not defined
    carry on ReferenceError: y is not defined

    3、Promise.prototype.finally()

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

    server.listen(port)
        .then(function(){
            // ...
    })
    .finally(server.stop)

    finally方法的回调函数不接受任何参数,这样没办法知道,前面的Promise状态到底是成功 fulfilled 还是 失败rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于Promise的执行结果

    promise
    .finally(() => {
      // 语句
    });
    
    // 等同于
    promise
    .then(
      result => {
        // 语句
        return result;
      },
      error => {
        // 语句
        throw error;
      }
    );
    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 })
      );
    };
    // resolve 的值是 undefined
    Promise.resolve(2).then(() => {}, () => {})
    
    // resolve 的值是 2
    Promise.resolve(2).finally(() => {})
    
    // reject 的值是 undefined
    Promise.reject(3).then(() => {}, () => {})
    
    // reject 的值是 3
    Promise.reject(3).finally(() => {})

    4、Promise.all()

    Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。

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

    Promise.all()方法接收一个数组作为参数,p1、p2、p3都是Promise实例。如果不是Promise实例,就会调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。

    p的状态由p1p2p3决定,分成两种情况。

    (1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

    (2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    注意:如果作为参数的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方法。

    const p1 = new Promise((resolve, reject) => {
      resolve('hello');
    })
    .then(result => result);
    
    const p2 = new Promise((resolve, reject) => {
      throw new Error('报错了');
    })
    .then(result => result);
    
    Promise.all([p1, p2])
    .then(result => console.log(result))
    .catch(e => console.log(e));
    //输出
    Error: 报错了

    5、Promise.race()

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

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

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

    栗子:在指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve

    var p = Promise.race([
        new Promise(function (resolve, reject) {
            setTimeout(() => resolve(123), 6000)
        }),
        new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('request timeout')), 5000)
        })
    ]);
    
    p
        .then(console.log)
        .catch(console.error);
    
    //输出
    Error: request timeout
    var p = Promise.race([
        new Promise(function (resolve, reject) {
            setTimeout(() => resolve(123), 2000)
        }),
        new Promise(function (resolve, reject) {
            setTimeout(() => reject(new Error('request timeout')), 5000)
        })
    ]);
    
    p
        .then(console.log)
        .catch(console.error);
    
    // 输出
    123

    6、Promise.resolve()

    将现有对象转为Promise对象。参数分为4种情况:

    1)参数是一个Promise实例

    2)参数是一个thenable对象

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

    4)不带有任何参数

    7、Promise.reject()

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

    四、应用

    加载图片,可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就会发生变化。

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

    Generator函数与Promise的结合

    使用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);
    
    //输出
    undefined
    foo

    async()/await()

    yuansheng js

    webassemble

  • 相关阅读:
    关于APP接口设计
    http协议详解-摘抄
    lnmp启动脚本
    MySQL数据库优化总结
    91、sendToTarget与sendMessage
    90、 Android UI模板设计
    Android Intent Action大汇总(转载)
    89、Android EditText 悬浮停靠
    88、android 插件开发教程(转载)
    Android Studio实用插件使用
  • 原文地址:https://www.cnblogs.com/songya/p/11689646.html
Copyright © 2020-2023  润新知